美文网首页
iOS原理(五)----block

iOS原理(五)----block

作者: 会笑的Even | 来源:发表于2018-12-27 00:57 被阅读0次

    iOS原理(五)----block

    block的本质

    block本质上也是一个OC对象,它内部也有个isa指针, block是封装了函数调用以及函数调用环境的OC对象.

    下面是简单的一个block代码:

            typedef  void(^MyBlock)(void);
            MyBlock block = ^{
                NSLog(@"---block---");
            };
            block();
    

    查看其编译生成的c++代码如下:

    typedef void(*MyBlock)(void);
            MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    // block执行逻辑的函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_6a9ca9_mi_0);
    }
    
    // block结构体
    struct __main_block_impl_0 {
        // block实现
      struct __block_impl impl;
        // block描述信息
      struct __main_block_desc_0* Desc;
        // 构造函数
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    // block实现结构体
    struct __block_impl {
        // isa指针,指向block类型
      void *isa;
      int Flags;
      int Reserved;
        // block执行逻辑的函数指针
      void *FuncPtr;
    };
    
    // block描述信息
    static struct __main_block_desc_0 {
      size_t reserved;
        // block的大小
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    

    从上面的源码可以看出:block其实就是一个带isa指针的结构体,结构体有两个成员变量:block实现的struct __block_impl impl和block信息的struct __main_block_desc_0* Desc,其中impl也是一个结构体,其成员变量有isa指针void *isa,int Flags,int Reserved和函数实现的函数指针oid *FuncPtr.Desc也是一个结构体,含有两个成员变量size_t reserved和block大小的size_t Block_size,所有block的结构如下:

    Snip20181112_17.png

    block对变量的捕获

    1.对局部auto变量的捕获

    代码如下:

            typedef  void(^MyBlock)(void);
            int age = 10;
            MyBlock block = ^{
                NSLog(@"---block---%d",age);
            };
            block();
    

    c++编译代码如下:

    Snip20181112_18.png Snip20181112_19.png Snip20181112_20.png

    可以出__main_block_impl_0结果体多了个age成员变量,且以值传递的方式.

    2.对局部非auto变量的捕获

    代码如下:

            typedef  void(^MyBlock)(void);
            static int age = 10;
            MyBlock block = ^{
                NSLog(@"---block---%d",age);
            };
            block();
    

    c++编译代码如下:

    Snip20181112_21.png Snip20181112_22.png Snip20181112_23.png

    可以出__main_block_impl_0结果体多了个*age成员变量,且以指针传递的方式.

    3.对全局变量的捕获

    代码如下:

    int age = 10;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            typedef  void(^MyBlock)(void);
           
            MyBlock block = ^{
                NSLog(@"---block---%d",age);
            };
            block();
        }
        return 0;
    }
    
    

    c++编译代码如下:

    Snip20181112_25.png

    可以出__main_block_impl_0并没有对全局变量age捕获,这是因为既然age是全局变量,也没有必要去捕获它,__main_block_func_0()函数能直接访问全局变量age.

    block的类型

    block类型分为三种:全局类型的_NSConcreteGlobalBlock,栈类型的_NSConcreteStackBlock和堆类型的_NSConcreteMallocBlock,即他们分别在内存中的数据区,堆区和栈区.

    Snip20181112_26.png

    1._NSConcreteGlobalBlock

    没有访问auto类型变量的block即为_NSConcreteGlobalBlock.代码如下:

            typedef  void(^MyBlock)(void);
            MyBlock block = ^{
                NSLog(@"---block---");
            };
            block();
            NSLog(@"%@",[block class]);
            NSLog(@"%@",[[block class] superclass]);
            NSLog(@"%@",[[[block class] superclass] superclass]);
            NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    

    打印如下:

    Snip20181112_27.png

    可以出此时的类型是_NSConcreteGlobalBlock,继承于NSBlockNSObject类型.

    2._NSConcreteStackBlock

    访问了auto类型的block即为_NSConcreteStackBlock.代码如下:

            typedef  void(^MyBlock)(void);
            int age = 10;
            MyBlock block = ^{
                NSLog(@"---block---%d",age);
            };
            block();
            NSLog(@"%@",[block class]);
            NSLog(@"%@",[[block class] superclass]);
            NSLog(@"%@",[[[block class] superclass] superclass]);
            NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    

    打印如下:

    Snip20181112_29.png

    可以出此时的类型是_NSConcreteStackBlock,继承于NSBlockNSObject类型.

    3._NSConcreteMallocBlock

    _NSConcreteStackBlock类型的block进行copy后类型即为_NSConcreteMallocBlock,代码如下:

            typedef  void(^MyBlock)(void);
            int age = 10;
            MyBlock block = [^{
                NSLog(@"---block---%d",age);
            } copy];
            block();
            NSLog(@"%@",[block class]);
            NSLog(@"%@",[[block class] superclass]);
            NSLog(@"%@",[[[block class] superclass] superclass]);
            NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    

    打印如下:

    Snip20181112_31.png

    可以出此时的类型是_NSConcreteMallocBlock,继承于NSBlockNSObject类型.

    block的copy

    1.如果对_NSConcreteGlobalBlock类型的进行copy,类型还是_NSConcreteGlobalBlock,代码如下:

            typedef  void(^MyBlock)(void);
            MyBlock block = [^{
                NSLog(@"---block---");
            } copy];
            block();
            NSLog(@"%@",[block class]);
            NSLog(@"%@",[[block class] superclass]);
            NSLog(@"%@",[[[block class] superclass] superclass]);
            NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
    

    打印如下:

    Snip20181112_32.png

    2.如果已经在堆上的_NSConcreteMallocBlock类型的block进行copy,该block的应用计数会加1.

    3.如果在栈上的_NSConcreteStackBlock类型的block进行copy,该类型为_NSConcreteMallocBlock.

    4.在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上:

    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时

    5 MRC下block属性的建议写法:@property (copy, nonatomic) void (^block)(void);

    6 ARC下block属性的建议写法:@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);都可以.

    auto类型的对象变量

    当block内部访问了外部auto类型的对象变量时:

    1.当block在栈上时:将不会对auto变量产生强引用.

    2.当block被拷贝到堆上:会调用block内部的copy函数, copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用.

    3.当block从堆上移除:会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的auto变量(release).

    __block修饰符

    __block可以用于解决block内部无法修改auto变量值的问题, __block不能修饰全局变量、静态变量(static).代码如下:

            typedef  void(^MyBlock)(void);
            __block int age = 10;
            MyBlock block = ^{
                age = 20;
                NSLog(@"---block---%d", age);
            };
            block();
    

    生成的c++的代码为:

     typedef void(*MyBlock)(void);
            // 生成一个新的对象age(结构体),类型为__Block_byref_age_0
            __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
            // block定义,就是指向__main_block_impl_0的指针
            MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
            
     // block逻辑执行函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
    
                (age->__forwarding->age) = 20;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_eb767f_mi_0, (age->__forwarding->age));
            }
            
     struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
        // 指向__Block_byref_age_0的指针
      __Block_byref_age_0 *age; // by ref
        // 构造函数
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    新生成包装age的结构体
    struct __Block_byref_age_0 {
      void *__isa;
        // 指向自己类型的指针
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
        // 原本的age变量
     int age;
    };
    
    // block描述
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    

    从上面的编译的c++的源码得知:__block修饰的age变量变成了一个对象,类型为struct __Block_byref_age_0结构体.

    __block的内存管理

    • 当block在栈上时,并不会对__block变量产生强引用.
    • 当block被copy到堆时,会调用block内部的copy函数, copy函数内部会调用_Block_object_assign函数, _Block_object_assign函数会对__block变量形成强引用(retain).
    Snip20181112_33.png
    • 当block从堆中移除时,会调用block内部的dispose函数, dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的__block变量(release)
    Snip20181112_34.png

    __block的__forwarding指针

    已知由__block修饰的变量生成的对象中有__Block_byref_age_0 *__forwarding成员变量,在栈上的block__forwarding指向自己,当被copy之后,栈上的block的__forwarding指向堆上的block,堆上的__forwarding指向自己.

    Snip20181112_35.png

    __block修饰对象

    • 当__block变量在栈上时,不会对指向的对象产生强引用.
    • 当__block变量被copy到堆时,会调用__block变量内部的copy函数, copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain).
    • 如果__block变量从堆上移除,会调用__block变量内部的dispose函数, dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放指向的对象(release).

    循环引用问题

    Animal定义一个block,实现并调用block:

    typedef  void(^MyBlock)(void);
    
    @interface Animal : NSObject
    
    @property (nonatomic,copy) MyBlock block;
    
    @end
    
    @implementation Animal
    
    - (void)dealloc {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    Animal *ani = [[Animal alloc] init];
    ani.block = ^{
        NSLog(@"%@",ani);
    };
    ani.block();
    

    运行代码,并没有回收ani对象,ani强引用了block,而block内部强引用了ani,导致对象得不到及时释放.

    Snip20181112_36.png

    我们可以用__weak(对象释放后为nil),__unsafe_unretained(对象释放后指向原来的内存).

            Animal *ani = [[Animal alloc] init];
            // 或者__unsafe_unretained
            __weak typeof(ani) weakAni = ani;
            ani.block = ^{
                NSLog(@"%@",weakAni);
            };
            ani.block();
    
    

    还可以用__block解决(必须要调用block).

            Animal *ani = [[Animal alloc] init];
            __block typeof(ani) weakAni = ani;
            ani.block = ^{
                NSLog(@"%@",weakAni);
                weakAni = nil;
            };
            ani.block();
    
    Snip20181112_37.png

    前面都是ARC情况下,在MRC情况下,只能用__unsafe_unretained和__block,此时用__block解决循环引用时,不需调用block,也不必将对象置为nil,这是因为在MRC下,__block下生成的对象,对变量时弱引用.

            Animal *ani = [[Animal alloc] init];
            __block typeof(ani) weakAni = ani;
            ani.block = ^{
                NSLog(@"%@",weakAni);
               
            };
            [ani release];
    

    相关文章

      网友评论

          本文标题:iOS原理(五)----block

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