OC中的Block

1.Block的本质

  • Block是从C语言的匿名函数发展而来,是通过函数的指针对函数进行调用的一种方式。
  • Block本质上也是一个OC对象,它内部也有个isa指针。
  • Runtime中Block的本质与类相同;底层代码中,Block会被解释为函数调用以及函数调用环境的OC相关的对象。
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __showBlockBase_block_impl_0 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_0* Desc;
  __showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __showBlockBase_block_func_0(struct __showBlockBase_block_impl_0 *__cself) {

        printf("hello word!\n");
    }

static struct __showBlockBase_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __showBlockBase_block_desc_0_DATA = { 0, sizeof(struct __showBlockBase_block_impl_0)};

// Block的定义和调用
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    // 定义Block
    void (*blockOne)(void) = ((void (*)())&__showBlockBase_block_impl_0((void *)__showBlockBase_block_func_0, &__showBlockBase_block_desc_0_DATA));
    // 调用Block
    ((void (*)(__block_impl *))((__block_impl *)blockOne)->FuncPtr)((__block_impl *)blockOne);
  }
}

  由于现在的环境基本上都是ARC环境,所以这篇文章默认是在ARC环境下的情况。

2.Block捕获变量

  OC中变量有5种:自动变量(局部变量)、函数的参数(函数形参)、静态变量(静态局部变量)、静态全局变量、全局变量;对于Block捕获自动变量时,还有可能被__block修饰的自动变量。
  当Block捕获静态全局变量、全局变量和未捕获外部的情况相同,底层代码如下;

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __showBlockBase_block_impl_0 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_0* Desc;
  __showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获静态变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,该指针类型的值类型与该静态变量的值类型相对应;

    // 局部静态变量
    static int staticValue = 0;

struct __showBlockBase_block_impl_3 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_3* Desc;
  int *staticValue;  // 捕获的外界局部静态变量
  __showBlockBase_block_impl_3(void *fp, struct __showBlockBase_block_desc_3 *desc, int *_staticValue, int flags=0) : staticValue(_staticValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获自动变量时,Block在底层解释为相关类时会增加一个的属性变量与该自动变量相对应;

    // 自动变量
    int value = 0;

struct __showBlockBase_block_impl_4 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_4* Desc;
  int value;  // 捕获的外界自动变量
  __showBlockBase_block_impl_4(void *fp, struct __showBlockBase_block_desc_4 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

  当Block捕获__block修饰的自动变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,且该属性变量的类型是该类型对应的一个类,类中的一个属性变量的值与该自动变量相对应。

  当这种Block从栈中Copy到堆中,需要进行三步,也叫Block的三层Copy

  1. 创建一个__Block_byref_i_0对象到堆中,该对象的存在一个属性,该属性为指针,指向__block修饰的变量的地址;
  2. 创建__showBlockBase_block_impl_5对象,即Block调用对象,该对象存在一个属性,该属性为指针,指向__Block_byref_i_0对象的地址;
  3. 栈Block拷贝到堆上,新增一个堆Block。
    // block变量
    __block int i = 0;

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __showBlockBase_block_impl_5 {
  struct __block_impl impl;
  struct __showBlockBase_block_desc_5* Desc;
  __Block_byref_i_0 *i;  // 捕获的外界__block修饰的自动变量
  __showBlockBase_block_impl_5(void *fp, struct __showBlockBase_block_desc_5 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

3.Block的类型

  Block根据在内存分区的位置,可以分为三类:全局Block(NSGlobalBlock)即在静态全局区的Block在程序的数据区域(.data区)、栈Block(NSStackBlock)即在栈上的Block、堆Block(NSMallocBlock)即在堆上的Block。
  当Block在捕获静态变量、静态全局变量、全局变量、未捕获外部变量时,为全局Block。

  栈Block的自动Copy到堆上的情况:
1.当Block代码块有强饮用时,包含局部引用和全局饮用,会自动Copy;
2.当Block做函数的返回值时,会自动Copy;
3.Cocoa框架下方法且方法名中含有usingBlock时,会自动Copy;
4.GCD中的包含Block的API,会自动Copy。
  在上述过程中会自动执行 Block_copy将原有的NSStakeBlock变成NSMallocBlock;但是在方法或者函数中Block作为参数时,不会自动Copy。

  对不同类型的Block进行Copy操作:
1.对全局Block进行手动Copy时,什么也没有发生;
2.栈Block通过copy操作,类似于深拷贝,将栈Block复制一份到堆区,堆区的Block就是堆Block;
3.堆Block的copy操作,类似于浅拷贝,copy的变量指针指向同一份的堆Block。

  当Block定义后没有赋值给某个变量,那它的类型就是NSStakeBlock。

4.Block的内存管理

  Block首次定义是在栈上,将声明的Block赋值给变量或者某个类的属性时,会调用objc_retainBlock方法,在该方法中会调用_Block_copy将栈上的Block复制到堆上;因此声明一个Block的属性一般使用copy关键字。
  栈上的Block在执行完毕后会立即释放,而堆上的Block通过ARC机制管理自己的内存。

5.Block的循环引用

  栈上的Block在执行完毕后就会释放,一般不会导致循环引用;我们所说的循环引用是堆上的Block形成。
  Block循环引用的原因:对象间接或者直接持有Block,Block中间接或者直接持有该对象,就会造成循环引用,导致对象和Block内存无法释放。
  避免Block循环引用有三种方法:
1.创建一个__weak修饰的指针变量,将造成循环引用的对象的赋值该指针变量,在Block中使用该指针变量,打破循环引用。代码如下:

    __weak typeof(self) weakSelf = self;
    self.myBlock3 = ^ {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", weakSelf);
        });
    };
    // 调用
    self.myBlock3();

2.在Block可执行完毕后将所持有对象设置为空,但是如果该Block没有执行,依旧不能打破循环引用。代码如下:

    __block UIViewController * vc = self;
    self.myBlock = ^(int sendValue) {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", vc);
            vc = nil;
        });
    };
    // 调用
    self.myBlock(2);

3.将持有对象作为Block的参数传入。代码如下:

    self.myBlock2 = ^(UIViewController *vc) {
        dispatch_after(2, dispatch_get_main_queue(), ^{
            NSLog(@"%@", vc);
        });
    };
    // 调用
    self.myBlock2(self);

6.Block的弱引用和多线程的问题

  在Block内部使用外部用__weak 修饰的弱引用。我们知道弱引用指向的对象在没有强引用引用的时候就会自动销毁,同时弱引用的指针赋值为 nil。那么如果在Block里面使用了多线程来访问弱引用,因多线程执行的时间顺序不定,会造成在多线程访问对象的时候这个弱引用的对象已经销毁了。

    Person * person = [[Person alloc] init];
    // 弱引用对象
    __weak typeof(Person) *weakPerson = person;
    person.name = @"zhou";

    person.block = ^int(int a, int b) {
        // 开辟一条线程
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 访问弱引用的对象
            NSLog(@"name = %@", weakPerson.name);
        });
        return 1;
    };
    person.block(1, 2);

  在Block中创建一个__strong修饰的指针变量,将外部的__weak的指针变量复制给该变量。

    Person * person = [[Person alloc] init];
    // 弱引用对象
    __weak typeof(Person) *weakPerson = person;
    person.name = @"zhou";
    person.block = ^int(int a, int b) {
        __strong typeof(Person) * strongPerson = weakPerson;
        // 开辟一条线程
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 访问弱引用的对象
            NSLog(@"name = %@", strongPerson.name);
        });
        return 1;
    };
    person.block(1, 2);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容