Block

作者: 雷霸龙 | 来源:发表于2021-03-03 17:32 被阅读0次

    1、block的定义和使用

    #import "ViewController.h"
    
    // 声明
    typedef void(^TestBlock1)(void);
    typedef void(^TestBlock2)(NSString *);
    typedef NSString * (^TestBlock3)(NSString * str, int a);
    typedef int(^TestBlock4)(void);
    
    @interface ViewController ()
    
    // 定义属性
    @property (nonatomic, copy) TestBlock1 testBlock1;
    @property (nonatomic, copy) TestBlock2 testBlock2;
    @property (nonatomic, copy) TestBlock3 testBlock3;
    @property (nonatomic, copy) TestBlock4 testBlock4;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        // block的定义和使用
        void (^MyBlockOne)(void) = ^(void) {
            NSLog(@"无参数无返回值");
        };
        MyBlockOne();
    
        void (^MyBlockTwo)(int) = ^(int a) {
            NSLog(@"a = %d我就是block,有参数,无返回值", a);
        };
        MyBlockTwo(100);
    
        int (^MyBlockThree)(int, int) = ^(int a, int b) {
            NSLog(@"%d我就是block,有参数,有返回值", a + b);
            return a + b;
        };
        MyBlockThree(2, 3);
    
        int (^MyBlockFour)(void) = ^ {
            NSLog(@"无参数,有返回值");
            return 45;
        };
        NSLog(@"%d", MyBlockFour());
        
        
        // 声明block的使用
        self.testBlock1 = ^ {
            NSLog(@"声明定义block1");
        };
        self.testBlock1();
        
        self.testBlock2 = ^(NSString * str) {
            NSLog(@"声明定义block2,参数是%@", str);
        };
        self.testBlock2(@"雷霸龙");
        
        self.testBlock3 = ^NSString * (NSString * str, int a){
            NSLog(@"声明定义block3");
            return [NSString stringWithFormat:@"%@%d", str, a];
        };
        NSLog(@"%@", self.testBlock3(@"雷霸龙", 888));
        
        self.testBlock4 = ^int{
            NSLog(@"声明定义block4");
            return 88;
        };
        NSLog(@"%d", self.testBlock4());
    }
    
    @end
    

    2、Block的底层基本结构

    void blockTest()
    {
        void (^block)(void) = ^{
            NSLog(@"Hello World!");
        };
        block();
    }
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            blockTest();
        }
    }
    

    通过clang命令查看编译器是如何实现Block的,在终端输入clang -rewrite-objc main.m,然后会在当前目录生成main.cpp的C++文件,代码如下:

    struct __blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __blockTest_block_desc_0* Desc;
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
        }
        
    
    static struct __blockTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
    
    void blockTest()
    {
        void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            blockTest();
        }
    }
    
    static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
    

    下面我们一个一个来看

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

    __blockTest_block_impl_0是Block的C++实现,是一个结构体,从命名可以看出表示blockTest中的第一个(0)Block。通常包含两个成员变量__block_impl impl__blockTest_block_desc_0* Desc和一个构造函数。

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

    __block_impl也是一个结构体

    • *isa:isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,本例中是_NSConcreteStackBlock类型。
    • Flags:block 的负载信息(引用计数和类型信息),按位存储。
    • Reserved:保留变量。
    • *FuncPtr:一个指针,指向Block执行时调用的函数,也就是Block需要执行的代码块。在本例中是__blockTest_block_func_0函数。
    __blockTest_block_desc_0
    static struct __blockTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
    

    __blockTest_block_desc_0是一个结构体,包含两个成员变量:

    • reserved:Block版本升级所需的预留区空间,在这里为0。
    • Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。

    __blockTest_block_desc_0_DATA是一个__blockTest_block_desc_0的一个实例。

    __blockTest_block_func_0

    __blockTest_block_func_0就是Block的执行时调用的函数,参数是一个__blockTest_block_impl_0类型的指针。

    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0);
        }
    
    blockTest
    void blockTest()
    {
        void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    

    第一部分,定义Block

    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA));
    

    我们看到block变成了一个指针,指向一个通过__blockTest_block_impl_0构造函数实例化的结构体__blockTest_block_impl_0实例,__blockTest_block_impl_0在初始化的时候需要两个个参数:

    __blockTest_block_func_0:Block块的函数指针。
    __blockTest_block_desc_0_DATA:作为静态全局变量初始化__main_block_desc_0的结构体实例指针。

    第二部分,调用Block

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)通过block->FuncPtr指针找到__blockTest_block_func_0函数并且转成(void (*)(__block_impl *))类型。
    ((__block_impl *)block)然后将block作为参数传给这个函数调用。

    Flags

    __block_impl中我们看到Flags,现在来详细讲一讲。
    在这里Block_private.h可以看到Flags的具体信息:

    // Values for Block_layout->flags to describe block objects
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
        BLOCK_IS_GC =             (1 << 27), // runtime
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
        BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
    };
    

    也就是说,一般情况下,一个 block 的 flags 成员默认设置为0。如果当 block 需要 Block_copy()Block_release 这类拷贝辅助函数,则会设置成 1 << 25 ,也就是 BLOCK_HAS_COPY_DISPOSE 类型。可以搜索到大量讲述 Block_copy 方法的博文,其中涉及到了 BLOCK_HAS_COPY_DISPOSE

    总结一下枚举类的用法,前 16 位即起到标记作用,又可记录引用计数:

    • BLOCK_DEALLOCATING:释放标记。一般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags ,告知该 block 可释放。
    • BLOCK_REFCOUNT_MASK:一般参与判断引用计数,是一个可选用参数。
    • BLOCK_NEEDS_FREE:通过设置该枚举位,来告知该 block 可释放。意在说明 block 是 heap block ,即我们常说的 _NSConcreteMallocBlock 。
    • BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数(a copy helper function)。
    • BLOCK_HAS_CTOR:是否拥有 block 析构函数(dispose function)。
    • BLOCK_IS_GC:是否启用 GC 机制(Garbage Collection)。
    • BLOCK_HAS_SIGNATURE:与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。

    3、block截获变量

    截获auto变量值
    image.png

    我们看到直接在block修改变量会提示错误,为什么呢?

    void blockTest()
    {
        int num = 10;
        void (^block)(void) = ^{
            NSLog(@"%d",num);
        };
        num = 20;
        block();
    }
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            blockTest();
        }
    }
    

    打印结果是10,clang改写后的代码如下:

    struct __blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __blockTest_block_desc_0* Desc;
      int num;
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
      int num = __cself->num; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
        }
        
        void blockTest()
    {
        int num = 10;
        void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num));
        num = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    

    __blockTest_block_impl_0多了一个成员变量int num;,再看看构造函数__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0),可以看到第三个参数只是变量的值,这也就解释了为什么打印的是10,因为block截获的是值。

    使用static修饰变量
    void blockTest()
    {
        static int num = 10;
        void (^block)(void) = ^{
            NSLog(@"%d",num);
            num = 30;
        };
        num = 20;
        block();
        NSLog(@"%d",num);
    }
    

    可以在block内部修改变量了,同时打印结果是20,30。clang改写后的代码如下:

    struct __blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __blockTest_block_desc_0* Desc;
      int *num;
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
      int *num = __cself->num; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
            (*num) = 30;
        }
        
        void blockTest()
    {
        static int num = 10;
        void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, &num));
        num = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    

    __blockTest_block_impl_0多了一个成员变量int *num;,和上面不同的是,这次block截获的是指针,所以可以在内部通过指针修改变量的值,同时在外部修改变量的值,block也能"感知到"。那么为什么之前传递指针呢?因为变量是栈上,作用域是函数blockTest内,那么有可能变量比block先销毁,这时候block再通过指针去访问变量就会有问题。而static修饰的变量不会被销毁,也就不用担心。

    全局变量
    int num = 10;
    
    void blockTest()
    {
        void (^block)(void) = ^{
            NSLog(@"%d",num);
            num = 30;
        };
        num = 20;
        block();
        NSLog(@"%d",num);
    }
    

    打印结果是20,30。clang改写后的代码如下:

    int num = 10;
    
    struct __blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __blockTest_block_desc_0* Desc;
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
            num = 30;
        }
    

    非常简单,在初始化__blockTest_block_impl_0并没有把num作为参数,__blockTest_block_func_0中也是直接访问全局变量。

    总结:

    变量类型 是否捕获到block内部 访问方式
    局部auto变量 值传递
    局部static变量 指针传递
    全局变量 直接访问
    使用__block修饰变量
    void blockTest()
    {
        __block int num = 10;
        void (^block)(void) = ^{
            NSLog(@"%d",num);
            num = 30;
        };
        num = 20;
        block();
        NSLog(@"%d",num);
    }
    

    效果和使用static修饰变量一样,clang改写后的代码如下:

    struct __Block_byref_num_0 {
      void *__isa;
    __Block_byref_num_0 *__forwarding;
     int __flags;
     int __size;
     int num;
    };
    
    struct __blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __blockTest_block_desc_0* Desc;
      __Block_byref_num_0 *num; // by ref
      __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
      __Block_byref_num_0 *num = __cself->num; // bound by ref
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num));
            (num->__forwarding->num) = 30;
        }
        
    static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __blockTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
      void (*dispose)(struct __blockTest_block_impl_0*);
    } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};
    
    void blockTest()
    {
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
        void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
        (num.__forwarding->num) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
    }
    

    __blockTest_block_impl_0多出来一个成员变量__Block_byref_num_0 *num;,我们看到经过__block修饰的变量类型变成了结构体__Block_byref_num_0__blockTest_block_impl_0多出来一个成员变量__Block_byref_num_0 *num;,block捕获的是__Block_byref_num_0类型指针。

    __Block_byref_num_0
    我们看到__Block_byref_num_0是一个结构体,并且有一个isa,因此我们可以知道它其实就是一个对象。同时还有一个__Block_byref_num_0 *类型的__forwardingnumnum我们能猜到就是用来保存变量的值,__forwarding就有一点复杂了,后面慢慢讲。

    __blockTest_block_copy_0和__blockTest_block_dispose_0
    __blockTest_block_copy_0中调用的是_Block_object_assign__blockTest_block_dispose_0中调用的是_Block_object_dispose

    函数 调用时机
    __blockTest_block_copy_0 __block变量结构体实例从栈拷贝到堆时
    __blockTest_block_dispose_0 __block变量结构体实例引用计数为0时

    关于_Block_object_assign_Block_object_dispose更详细代码可以在runtime.c 中查看。

    BLOCK_FIELD_IS_BYREF
    我们看到_Block_object_assign_Block_object_dispose中都有个参数值为8,BLOCK_FIELD_IS_BYREF类型,什么意思呢?在Block_private.h 中可以查看到:

    // Runtime support functions used by compiler when generating copy/dispose helpers
    
    // Values for _Block_object_assign() and _Block_object_dispose() parameters
    enum {
        // see function implementation for a more complete description of these fields and combinations
        BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
        BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
        BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
        BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
        BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
    };
    
    • BLOCK_FIELD_IS_OBJECT:OC对象类型
    • BLOCK_FIELD_IS_BLOCK:是一个block
    • BLOCK_FIELD_IS_BYREF:在栈上被__block修饰的变量
    • BLOCK_FIELD_IS_WEAK:被__weak修饰的变量,只在Block_byref管理内部对象内存时使用
    • BLOCK_BYREF_CALLER:处理Block_byref内部对象内存的时候会加的一个额外标记(告诉内部实现不要进行retain或者copy)

    __blockTest_block_desc_0
    我们可以看到它多了两个回调函数指针*copy*dispose,这两个指针会被赋值为__main_block_copy_0__main_block_dispose_0

    最后我们看到访问num是这样的:

    4、Block的内存管理

    在前面我们讲到__block_impl指向的_NSConcreteStackBlock类型的类对象,其实总共有三种类型:

    类型 存储区域
    _NSConcreteStackBlock
    _NSConcreteGlobalBlock 数据区
    _NSConcreteMallocBlock

    前面也讲到copydispose,在ARC环境下,有哪些情况编译器会自动将栈上的把Block从栈上复制到堆上呢?

    • 调用Block的copy实例方法时
    • Block作为函数返回值返回时
    • 在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候
    • 将block赋给带有__strong修饰符的id类型或者Block类型时

    Bock从栈中复制到堆,__block也跟着变化:

    image.png
    1. 当Block在栈上时,__block的存储域是栈,__block变量被栈上的Block持有。
    2. 当Block被复制到堆上时,会通过调用Block内部的copy函数,copy函数内部会调用_Block_object_assign函数。此时__block变量的存储域是堆,__block变量被堆上的Block持有。
    3. 当堆上的Block被释放,会调用Block内部的dispose,dispose函数内部会调用_Block_object_dispose,堆上的__block被释放。
    image.png
    1. 当多个栈上的Block使用栈上的__block变量,__block变量被栈上的多个Block持有。
    2. 当Block0被复制到堆上时,__block也会被复制到堆上,被堆上Block0持有。Block1仍然持有栈上的__block,原栈上__block变量的__forwarding指向拷贝到堆上之后的__block变量。
    3. 当Block1也被复制到堆上时,堆上的__block被堆上的Block0和Block1只有,并且__block的引用计数+1。
    4. 当堆上的Block都被释放,__block变量结构体实例引用计数为0,调用_Block_object_dispose,堆上的__block被释放。

    下图是描述__forwarding变化。这也就能解释__forwarding存在的意义:
    __forwarding 保证在栈上或者堆上都能正确访问对应变量

    image.png
    int main(int argc, char * argv[]) {
    
        int num = 10;
    
        NSLog(@"%@",[^{
            NSLog(@"%d",num);
        } class]);
    
        void (^block)(void) = ^{
            NSLog(@"%d",num);
        };
    
        NSLog(@"%@",[block class]);
    }
    

    打印结果:

    2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
    2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__
    

    我们可以看到第一个Block没有赋值给__strong指针,而第二个Block赋值给__strong指针,所以第一个在栈上,而第二个在堆上。

    5、Block截获对象

    @interface Person : NSObject
    @property (nonatomic, strong) NSString *name;
    @end
    
    @implementation Person
    
    - (void)dealloc {
        NSLog(@"-------dealloc-------");
    }
    
    @end
    
    typedef void(^Block)(void);
    
    int main(int argc, char * argv[]) {
        {
            Person *person = [[Person alloc] init];
            person.name = @"roy";
    
            NSLog(@"%@",[^{
                NSLog(@"%@",person.name);
            } class]);
            NSLog(@"%@",@"+++++++++++++");
        }
    }
    

    打印结果:

    2021-03-04 16:10:06.374697+0800 test[2489:129031] __NSStackBlock__
    2021-03-04 16:10:06.374824+0800 test[2489:129031] +++++++++++++
    2021-03-04 16:10:06.374932+0800 test[2489:129031] -------dealloc-------
    

    我们看到当Block内部访问了对象类型的auto对象时,如果Block是在栈上,将不会对auto对象产生强引用。

    auto Strong 对象
    typedef void(^Block)(void);
    
    int main(int argc, char * argv[]) {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.name = @"roy";
    
            block = ^{
                NSLog(@"%@",person.name);
            };
            person.name = @"david";
            NSLog(@"%@",@"+++++++++++++");
        }
        NSLog(@"%@",@"------------");
        block ();
    }
    

    打印结果:

    2019-05-04 17:46:27.083280+0800 BlockTest[33745:16864251] +++++++++++++
    2019-05-04 17:46:27.083934+0800 BlockTest[33745:16864251] ------------
    2019-05-04 17:46:27.084018+0800 BlockTest[33745:16864251] david
    2019-05-04 17:46:27.084158+0800 BlockTest[33745:16864251] -------dealloc-------
    

    我们看到是先打印的david再调用Person的析构方法dealloc,在终端输入clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m -fobjc-arc,clang在ARC环境下改写后的代码如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__strong person;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    我们看到__main_block_impl_0中的Person *__strong person;成员变量。
    Block截获了auto对象,当Block被拷贝到堆上,Block强引用auto对象,这就能解释了为什么超出了person的作用域,person没有立即释放,当Block释放之后,会自动去掉对该对象的强引用,该对象就会被释放了。

    auto Weak 对象
    typedef void(^Block)(void);
    
    int main(int argc, char * argv[]) {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.name = @"roy";
            __weak Person *weakPerson = person;
    
            block = ^{
                NSLog(@"%@",weakPerson.name);
            };
            weakPerson.name = @"david";
            NSLog(@"%@",@"+++++++++++++");
        }
        NSLog(@"%@",@"------------");
        block ();
    }
    

    打印结果:

    2019-05-04 17:49:38.858554+0800 BlockTest[33856:16869229] +++++++++++++
    2019-05-04 17:49:38.859218+0800 BlockTest[33856:16869229] -------dealloc-------
    2019-05-04 17:49:38.859321+0800 BlockTest[33856:16869229] ------------
    2019-05-04 17:49:38.859403+0800 BlockTest[33856:16869229] (null)
    

    直接在终端输入clang -rewrite-objc main.m会报cannot create __weak reference because the current deployment target does not support weak ref错误。需要用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m,-fobjc-arc代表当前是ARC环境 -fobjc-runtime=macosx-10.13:代表当前运行时环境,缺一不可,clang之后的代码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *__weak weakPerson;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    我们看到__main_block_impl_0中的Person *__weak weakPerson;成员变量。

    总结:

    • 当Block内部访问了对象类型的auto对象时,如果Block是在栈上,将不会对auto对象产生强引用。
    • 如果block被拷贝到堆上,会调用Block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign会根据auto对象的修饰符(__strong,__weak,__unsafe_unretained)做出相应的操作,当使用的是__strong时,将会对person对象的引用计数加1,当为__weak时,引用计数不变。
    • 如果Block从对上移除,会调用block内部的dispose函数,内部会调用_Block_object_dispose函数,这个函数会自动释放引用的auto对象。

    6、Block循环引用

    @interface Person : NSObject
    
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, copy) void (^block)(void);
    
    - (void)testReferenceSelf;
    
    @end
    
    @implementation Person
    
    - (void)testReferenceSelf {
        self.block = ^ {
            NSLog(@"self.name = %s", self.name.UTF8String);
        };
        self.block();
    }
    
    - (void)dealloc {
        NSLog(@"-------dealloc-------");
    }
    
    @end
    
    
    int main(int argc, char * argv[]) {
        Person *person = [[Person alloc] init];
        person.name = @"roy";
        [person testReferenceSelf];
    }
    

    打印结果是self.name = roy,Person的析构方法dealloc并没有执行,这是典型的循环引用,下面我们研究研究为啥会循环引用。clang改写后的代码如下:

    struct __Person__testReferenceSelf_block_impl_0 {
      struct __block_impl impl;
      struct __Person__testReferenceSelf_block_desc_0* Desc;
      Person *const __strong self;
      __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __strong _self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
        ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344)));
        ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
    }
    

    我们看到本来Person中testReferenceSelf方法是没有参数的,但是转成C++之后多出来两个参数:* self_cmd,再看看__Person__testReferenceSelf_block_impl_0中多出来一个成员变量Person *const __strong self;,因此我们知道Person中block捕获了self,block强引用self,同时self也强引用block,因此形成循环引用。

    Weak解除循环引用

    @implementation Person
    
    - (void)testReferenceSelf {
        __weak typeof(self) weakself = self;
        self.block = ^ {
            __strong typeof(self) strongself = weakself;
            NSLog(@"self.name = %s", strongself.name.UTF8String);
        };
        self.block();
    }
    
    - (void)dealloc {
        NSLog(@"-------dealloc-------");
    }
    
    @end
    

    打印结果:

    2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy
    2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc-------
    

    我们看到Person对象被正常释放了,说明不存在循环引用,为什么呢?clang改写后的代码如下:

    struct __Person__testReferenceSelf_block_impl_0 {
      struct __block_impl impl;
      struct __Person__testReferenceSelf_block_desc_0* Desc;
      Person *const __weak weakself;
      __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) {
        __attribute__((objc_ownership(weak))) typeof(self) weakself = self;
        ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344)));
        ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))();
    }
    

    可以看到__Person__testReferenceSelf_block_impl_0结构体中weakself成员是一个__weak修饰的Person类型对象,也就是说__Person__testReferenceSelf_block_impl_0对Person的依赖是弱依赖。weak修饰变量是在runtime中进行处理的,在Person对象的Dealloc方法中会调用weak引用的处理方法,从weak_table中寻找弱引用的依赖对象,进行清除处理。

    相关文章

      网友评论

          本文标题:Block

          本文链接:https://www.haomeiwen.com/subject/qbwqqltx.html