• 深入理解iOS的block (下)


    对象类型的auto变量

    例子一

    首先看一个简单的例子
    定义一个类 YZPerson,里面只有一个dealloc方法

    @interface YZPerson : NSObject
    @property (nonatomic ,assign) int age;
    @end
    
    @implementation YZPerson
    
    - (void)dealloc
    {
     NSLog(@"%s",__func__);
    }
    
    @end
    

    如下代码使用

    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     {
     YZPerson *person = [[YZPerson alloc]init];
     person.age = 10;
     }
     NSLog(@"-----");
     }
     return 0;
    }
    

    想必大家都能知道会输出什么,没错,就是person先销毁,然后打印----- 因为person是在大括号内,当大括号执行完之后,person 就销毁了。

    iOS-block[1376:15527] -[YZPerson dealloc]
    iOS-block[1376:15527] -----
    

    例子二

    上面的例子,是不是挺简单,那下面这个呢,

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZBlock block;
    
     {
     YZPerson *person = [[YZPerson alloc]init];
     person.age = 10;
    
     block = ^{
     NSLog(@"---------%d", person.age);
     };
    
     NSLog(@"block.class = %@",[block class]);
     }
     NSLog(@"block销毁");
    
     }
     return 0;
    }
    

    如下结果,输出可知当 block为__NSMallocBlock__类型时候,block可以保住person的命的,因为person离开大括号之后没有销毁,当block销毁,person才销毁

    iOS-block[3186:35811] block.class = __NSMallocBlock__
    iOS-block[3186:35811] block销毁
    iOS-block[3186:35811] -[YZPerson dealloc]
    

    一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:1012951431, 分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。

    分析

    终端执行这行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mmain.m生成main.cpp
    可以 看到如下代码

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

    很明显就是这个block里面包含 YZPerson *person

    MRC下 block引用实例对象

    上面的例子,是不是挺简单,那如果是MRC下呢

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZBlock block;
    
     {
     YZPerson *person = [[YZPerson alloc]init];
     person.age = 10;
    
     block = ^{
     NSLog(@"---------%d", person.age);
     };
    
     NSLog(@"block.class = %@",[block class]);
    
     // MRC下,需要手动释放
     [person release];
     }
     NSLog(@"block销毁");
     // MRC下,需要手动释放
     [block release];
     }
     return 0;
    }
    

    输出结果为

    iOS-block[3114:34894] block.class = __NSStackBlock__
    iOS-block[3114:34894] -[YZPerson dealloc]
    iOS-block[3114:34894] block销毁
    

    和上面的对比,区别就是,还没有执行NSLog(@"block销毁");的时候,[YZPerson dealloc]已经执行了。也就是说,person 离开大括号,就销毁了。

    输出可知当 block为__NSStackBlock__类型时候,block不可以保住person的命的

    MRC下 [block copy]引用实例对象

    在MRC下,对block执行了copy操作

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZBlock block;
    
     {
     YZPerson *person = [[YZPerson alloc]init];
     person.age = 10;
    
     block = [^{
     NSLog(@"---------%d", person.age);
     } copy];
    
     NSLog(@"block.class = %@",[block class]);
     // MRC下,需要手动释放
     [person release];
     }
    
     NSLog(@"block销毁");
     [block release];
     }
     return 0;
    

    输出结果为,可知当 block为__NSMallocBlock__类型时候,block是可以保住person的命的

    iOS-block[3056:34126] block.class = __NSMallocBlock__
    iOS-block[3056:34126] block销毁
    iOS-block[3056:34126] -[YZPerson dealloc]
    

    __weak修饰

    • 如下代码
    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZBlock block;
    
     {
     YZPerson *person = [[YZPerson alloc]init];
     person.age = 10;
    
     __weak YZPerson *weakPerson = person;
    
     block = ^{
     NSLog(@"---------%d", weakPerson.age);
     };
    
     NSLog(@"block.class = %@",[block class]);
     }
    
     NSLog(@"block销毁");
     }
     return 0;
    }
    
    • 输出为
    iOS-block[3687:42147] block.class = __NSMallocBlock__
    iOS-block[3687:42147] -[YZPerson dealloc]
    iOS-block[3687:42147] block销毁
    
    • 生成cpp文件

    • 注意:

    • 在使用clang转换OC为C++代码时,可能会遇到以下问题
      cannot create __weak reference in file using manual reference

    • 解决方案:支持ARC、指定运行时系统版本,比如
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

    生成之后,可以看到,如下代码,MRC情况下,生成的代码明显多了,这是因为ARC自动进行了copy操作

    //copy 函数
     void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    
     //dispose函数
     void (*dispose)(struct __main_block_impl_0*);
    
    struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     //weak修饰
     YZPerson *__weak weakPerson;
     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
     }
    };
    
    static struct __main_block_desc_0 {
     size_t reserved;
     size_t Block_size;
     //copy 函数
     void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    
     //dispose函数
     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
    };
    
    //copy函数内部会调用_Block_object_assign函数
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    
    //asssgin会对对象进行强引用或者弱引用
    _Block_object_assign((void*)&dst->person, 
    (void*)src->person, 
    3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    //dispose函数内部会调用_Block_object_dispose函数
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 
    3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    

    小结

    无论是MAC还是ARC

    • 当block为__NSStackBlock__类型时候,是在栈空间,无论对外面使用的是strong 还是weak 都不会对外面的对象进行强引用
    • 当block为__NSMallocBlock__类型时候,是在堆空间,block是内部的_Block_object_assign函数会根据strong或者 weak对外界的对象进行强引用或者弱引用。

    其实也很好理解,因为block本身就在栈上,自己都随时可能消失,怎么能保住别人的命呢?

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

      • 如果block是在栈上,将不会对auto变量产生强引用
    • 如果block被拷贝到堆上

      • 会调用block内部的copy函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
    • 如果block从堆上移除

      • 会调用block内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放引用的auto变量(release)
    函数调用时机
    copy函数 栈上的Block复制到堆上
    dispose函数 堆上的block被废弃时

    __block

    先从一个简单的例子说起,请看下面的代码

    // 定义block
    typedef void (^YZBlock)(void);
    
    int age = 10;
    YZBlock block = ^{
     NSLog(@"age = %d", age);
    };
    block();
    

    代码很简单,运行之后,输出

    age = 10

    上面的例子在block中访问外部局部变量,那么问题来了,如果想在block内修改外部局部的值,怎么做呢?

    修改局部变量的三种方法

    写成全局变量

    我们把a定义为全局变量,那么在哪里都可以访问,

    // 定义block
    typedef void (^YZBlock)(void);
     int age = 10;
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZBlock block = ^{
     age = 20;
     NSLog(@"block内部修改之后age = %d", age);
     };
    
     block();
     NSLog(@"block调用完 age = %d", age);
     }
     return 0;
    }
    

    这个很简单,输出结果为

    block内部修改之后age = 20
    block调用完 age = 20
    

    对于输出就结果也没什么问题,因为全局变量,是所有地方都可访问的,在block内部可以直接操作age的内存地址的。调用完block之后,全局变量age指向的地址的值已经被更改为20,所以是上面的打印结果

    static修改局部变量

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
     static int age = 10;
     YZBlock block = ^{
     age = 20;
     NSLog(@"block内部修改之后age = %d", age);
     };
    
     block();
     NSLog(@"block调用完 age = %d", age);
     }
     return 0;
    }
    

    上面的代码输出结果为

    block内部修改之后age = 20
    block调用完 age = 20
    

    终端执行这行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mmain.m生成main.cpp
    可以 看到如下代码

    struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     int *age;
     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
     }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     int *age = __cself->age; // bound by copy
    
     (*age) = 20;
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_5dbaa1_mi_0, (*age));
    }
    

    可以看出,当局部变量用static修饰之后,这个block内部会有个成员是int *age,也就是说把age的地址捕获了。这样的话,当然在block内部可以修改局部变量age了。

    • 以上两种方法,虽然可以达到在block内部修改局部变量的目的,但是,这样做,会导致内存无法释放。无论是全局变量,还是用static修饰,都无法及时销毁,会一直存在内存中。很多时候,我们只是需要临时用一下,当不用的时候,能销毁掉,那么第三种,也就是今天的主角 __block隆重登场

    __block来修饰

    代码如下

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
     __block int age = 10;
     YZBlock block = ^{
     age = 20;
     NSLog(@"block内部修改之后age = %d",age);
     };
    
     block();
     NSLog(@"block调用完 age = %d",age);
     }
     return 0;
    }
    

    输出结果和上面两种一样

    block内部修改之后age = 20
    block调用完 age = 20
    

    __block分析

    • 终端执行这行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mmain.m生成main.cpp

    首先能发现 多了__Block_byref_age_0结构体

    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
     // fp是函数地址  desc是描述信息  __Block_byref_age_0 类型的结构体  *_age  flags标记
     __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; //fp是函数地址
     Desc = desc;
     }
    };
    

    再仔细看结构体__Block_byref_age_0,可以发现第一个成员变量是isa指针,第二个是指向自身的指针__forwarding

    
    // 结构体 __Block_byref_age_0
    struct __Block_byref_age_0 {
     void *__isa; //isa指针
     __Block_byref_age_0 *__forwarding; // 指向自身的指针
     int __flags;
     int __size;
     int age; //使用值
    };
    

    查看main函数里面的代码

     // 这是原始的代码 __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代码
    YZBlock block = ((void (*)())&__main_block_impl_0(
    (void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
    

    代码太长,简化一下,去掉一些强转的代码,结果如下

    // 这是原始的代码 __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_byref_age_0
    __Block_byref_age_0 age = {
     0, //赋值给 __isa
     (__Block_byref_age_0 *)&age,//赋值给 __forwarding,也就是自身的指针
     0, // 赋值给__flags
     sizeof(__Block_byref_age_0),//赋值给 __size
     10 // age 使用值
     };
    
    // 这是原始的 block代码
    YZBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
    
    // 这是简化之后的 block代码
    YZBlock block = (&__main_block_impl_0(
     __main_block_func_0,
     &__main_block_desc_0_DATA,
     &age,
     570425344));
    
     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
     //简化为
    block->FuncPtr(block);
    

    其中__Block_byref_age_0结构体中的第二个(__Block_byref_age_0 *)&age赋值给上面代码结构体__Block_byref_age_0中的第二个__Block_byref_age_0 *__forwarding,所以__forwarding 里面存放的是指向自身的指针

    //这是简化之后的代码 __Block_byref_age_0
    __Block_byref_age_0 age = {
     0, //赋值给 __isa
     (__Block_byref_age_0 *)&age,//赋值给 __forwarding,也就是自身的指针
     0, // 赋值给__flags
     sizeof(__Block_byref_age_0),//赋值给 __size
     10 // age 使用值
     };
    

    结构体__Block_byref_age_0中代码如下,第二个__forwarding存放指向自身的指针,第五个age里面存放局部变量

    // 结构体 __Block_byref_age_0
    struct __Block_byref_age_0 {
     void *__isa; //isa指针
     __Block_byref_age_0 *__forwarding; // 指向自身的指针
     int __flags;
     int __size;
     int age; //使用值
    };
    

    调用的时候,先通过__forwarding找到指针,然后去取出age值。

    (age->__forwarding->age));
    
     
     

    小结

    • __block可以用于解决block内部无法修改auto变量值的问题

    • __block不能修饰全局变量、静态变量(static)

      • 编译器会将__block变量包装成一个对象

    调用的是,从__Block_byref_age_0的指针找到 age所在的内存,然后修改值

     

    内存管理问题

    bloc访问OC对象

    代码如下

    当block内部访问外面的OC对象的时候

    eg:

    // 定义block
    typedef void (^YZBlock)(void);
    
    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     NSObject *obj = [[NSObject alloc]init];
     YZBlock block = ^{
     NSLog(@"%p",obj);
     };
     block();
     }
     return 0;
    }
    

    在终端使用clang转换OC为C++代码

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    
     

    因为是在ARC下,所以会copy,栈上拷贝到堆上,结构体__main_block_desc_0中有copydispose

    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*);
    }
    

    copy会调用 __main_block_copy_0

    static void __main_block_copy_0(struct __main_block_impl_0*dst, 
    struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, 
    (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    

    其内部的_Block_object_assign会根据代码中的修饰符 strong或者weak而对其进行强引用或者弱引用。

    查看__main_block_impl_0

    struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     //strong 强引用
     NSObject *__strong obj;
     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
     }
    };
    

    可以看上修饰符是strong,所以,调用_Block_object_assign时候,会对其进行强引用。

    由前面可知

    • 当block在栈上时,并不会对__block变量产生强引用

    • 当block被copy到堆时

      • 会调用block内部的copy函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会对__block变量形成强引用(retain)
    • 当block从堆中移除时

      • 会调用block内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放引用的__block变量(release)

    拷贝

    拷贝的时候,

    • 会调用block内部的copy函数

      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会对__block变量形成强引用(retain)

      中我们知道,如下代码

    __block int age = 10;
     YZBlock block = ^{
     age = 20;
     NSLog(@"block内部修改之后age = %d",age);
     };
    

    局部变量age是在栈上的,在block内部引用age,但是当block从栈上拷贝到堆上的时候,怎么能保证下次block访问age的时候,能访问到呢?因为我们知道栈上的局部变量,随时会销毁的。

    假设现在有两个栈上的block,分别是block0和block1,同时引用了了栈上的__block变量。现在对block0进行copy操作,我们知道,栈上的block进行copy,就会复制到堆上,也就是说block0会复制到堆上,因为block0持有__block变量,所以也会把这个__block变量复制到堆上,同时堆上的block0对堆上的__block变量是强引用,这样能达到block0随时能访问__block变量

     

    还是上面的例子,刚才block0拷贝到堆上了,现在如果block1也拷贝到堆上,因为刚才变量已经拷贝到堆上,就不需要再次拷贝,只需要把堆上的block1也强引用堆上的变量就可以了。

     

    释放

    当释放的时候

    • 会调用block内部的dispose函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放引用的__block变量(release)

    上面的代码中,如果在堆上只有一个block引用__block变量,当block销毁时候,直接销毁堆上的__block变量,但是如果有两个block引用__block变量,就需要当两个block都废弃的时候,才会废弃__block变量

     

    其实,说到底,就是谁使用,谁负责

    对象类型的auto变量__block变量

    把前面的都放在一起整理一下,有 auto 变量 num , __block变量int, obj 和weakObj2如下

     __block int age = 10;
     int num = 8;
     NSObject *obj = [[NSObject alloc]init];
     NSObject *obj2 = [[NSObject alloc]init];
     __weak NSObject *weakObj2 = obj2;
     YZBlock block = ^{
     NSLog(@"age = %d",age);
     NSLog(@"num = %d",num);
     NSLog(@"obj = %p",obj);
     NSLog(@"weakObj2 = %p",weakObj2);
     NSLog(@"block内部修改之后age = %d",age);
     };
    
    block();
    

    执行终端指令

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    

    生成代码如下所示

     

    被__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)

    __block__forwarding指针

    //结构体__Block_byref_obj_0中有__forwarding
     struct __Block_byref_obj_0 {
     void *__isa;
     __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *__strong obj;
    };
    
    // 访问的时候
    age->__forwarding->age
    

    为啥什么不直接用age,而是age->__forwarding->age呢?

    这是因为,如果__block变量在栈上,就可以直接访问,但是如果已经拷贝到了堆上,访问的时候,还去访问栈上的,就会出问题,所以,先根据__forwarding找到堆上的地址,然后再取值

     

    总结

    • 当block在栈上时,对它们都不会产生强引用

    • 当block拷贝到堆上时,都会通过copy函数来处理它们

      • __block变量(假设变量名叫做a)
    • _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

    • 对象类型的auto变量(假设变量名叫做p)
      _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

    • 当block从堆上移除时,都会通过dispose函数来释放它们
      __block变量(假设变量名叫做a)
      _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

    • 对象类型的auto变量(假设变量名叫做p)
      _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

    循环引用问题

    继续探索一下block的循环引用问题。

    看如下代码,有个Person类,里面两个属性,分别是block和age

    #import <Foundation/Foundation.h>
    
    typedef void (^YZBlock) (void);
    
    @interface YZPerson : NSObject
    @property (copy, nonatomic) YZBlock block;
    @property (assign, nonatomic) int age;
    @end
    
    #import "YZPerson.h"
    
    @implementation YZPerson
    - (void)dealloc
    {
     NSLog(@"%s", __func__);
    }
    @end
    

    main.m中如下代码

    int main(int argc, const char * argv[]) {
     @autoreleasepool {
    
     YZPerson *person = [[YZPerson alloc] init];
     person.age = 10;
     person.block = ^{
     NSLog(@"person.age--- %d",person.age);
     };
     NSLog(@"--------");
    
     }
     return 0;
    }
    

    输出只有

    iOS-block[38362:358749] ——–

    也就是说程序结束,person都没有释放,造成了内存泄漏。

    循环引用原因

    下面这行代码,是有个person指针,指向了YZPerson对象

    YZPerson *person = [[YZPerson alloc] init];
    

    执行完

    person.block = ^{
     NSLog(@"person.age--- %d",person.age);
     };
    

    之后,block内部有个强指针指向person,下面代码生成cpp文件

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

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

    而block是person的属性

    @property  (copy, nonatomic) YZBlock block;
    

    当程序退出的时候,局部变量person销毁,但是由于MJPerson和block直接,互相强引用,谁都释放不了。

     
    i

    __weak解决循环引用

    为了解决上面的问题,只需要用__weak来修饰,即可

    int main(int argc, const char * argv[]) {
     @autoreleasepool {
     YZPerson *person = [[YZPerson alloc] init];
     person.age = 10;
    
     __weak YZPerson *weakPerson = person;
    
     person.block = ^{
     NSLog(@"person.age--- %d",weakPerson.age);
     };
     NSLog(@"--------");
    
     }
     return 0;
    }
    

    编译完成之后是

    struct __main_block_impl_0 {
     struct __block_impl impl;
     struct __main_block_desc_0* Desc;
     // block内部对weakPerson是弱引用
     YZPerson *__weak weakPerson;
     __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
     }
    };
    

    当局部变量消失时候,对于YZPseson来说,只有一个若指针指向它,那它就销毁,然后block也销毁。

    __unsafe_unretained解决循环引用

    除了上面的__weak之后,也可以用__unsafe_unretained来解决循环引用

    int main(int argc, const char * argv[]) {
     @autoreleasepool {
     YZPerson *person = [[YZPerson alloc] init];
     person.age = 10;
    
     __unsafe_unretained YZPerson *weakPerson = person;
    
     person.block = ^{
     NSLog(@"person.age--- %d",weakPerson.age);
     };
     NSLog(@"--------");
    
     }
     return 0;
    }
    

    对于的cpp文件为

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

    虽然__unsafe_unretained可以解决循环引用,但是最好不要用,因为

    • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
    • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

    __block解决循环引用

    eg:

    int main(int argc, const char * argv[]) {
     @autoreleasepool {
     __block YZPerson *person = [[YZPerson alloc] init];
     person.age = 10;
     person.block = ^{
     NSLog(@"person.age--- %d",person.age);
     //这一句不能少
     person = nil;
     };
     // 必须调用一次
     person.block();
     NSLog(@"--------");
     }
     return 0;
    }
    

    上面的代码中,也是可以解决循环引用的。但是需要注意的是,person.block();必须调用一次,为了执行person = nil;.

    对应的结果如下

    • 下面的代码,block会对__block产生强引用
    __block YZPerson *person = [[YZPerson alloc] init];
    person.block = ^{
     NSLog(@"person.age--- %d",person.age);
     //这一句不能少
     person = nil;
    };
    
    • person对象本身就对block是强引用
    @property  (copy, nonatomic) YZBlock block;
    
    • __block对person产生强引用
    struct __Block_byref_person_0 {
     void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     //`__block`对person产生强引用
     YZPerson *__strong person;
    };
    

    所以他们的引用关系如图

     

    当执行完person = nil时候,__block解除对person的引用,进而,全都解除释放了。
    但是必须调用person = nil才可以,否则,不能解除循环引用

    小结

    通过前面的分析,我们知道,ARC下,上面三种方式对比,最好的是__weak

    MRC下注意点

    如果再MRC下,因为不支持弱指针__weak,所以,只能是__unsafe_unretained或者__block来解决循环引用

    结束

    回到最开始的问题

    • block的原理是怎样的?本质是什么?

    • __block的作用是什么?有什么使用注意点?

    • block的属性修饰词为什么是copy?使用block有哪些使用注意?

    • block一旦没有进行copy操作,就不会在堆上

    • block在修改NSMutableArray,需不需要添加__block?

    现在是不是心中有了自己的答案呢?

    另外,如果你想一起进阶,不妨添加一下交流群1012951431,选择加入一起交流,一起学习。期待你的加入!

     
  • 相关阅读:
    Day 83 VUE——组件、插槽、生命周期
    Power BI制作GDP动态排行榜
    SQL中为什么经常要加WITH(NOLOCK)
    ant-desin-vue——table全选时自定义的禁用行也被选上,且最后一行不选中问题
    ant-design-vue——a-select下拉框值为空字串时,高度偏窄问题
    ant-desgin-vue——tree自定义节点不可选用的置灰或禁用
    js——在A页面打开新页面B,关闭新页面B后刷新A页面
    Vue You may have an infinite update loop in a component render function.
    js实现继承的方法中为何总是要修正constructor方法的指向呢?
    Spring boot 扫描不到 mybatis 接口包
  • 原文地址:https://www.cnblogs.com/Julday/p/12461934.html
Copyright © 2020-2023  润新知