【iOS】Block底层分析

打印 上一主题 下一主题

主题 796|帖子 796|积分 2388


媒介

Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,由于它内部有isa指针
Block的基本利用请看这两篇文章:


  • k
  • l
本篇文章偏重探究Block这些特性的底层原理
Block底层结构

声明一个最简单的块并调用:
  1. void (^block)(void) = ^{
  2.     NSLog(@"Hello World!");
  3. };
  4. block();
复制代码
利用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:
  1. // 原本的代码有各种强制转换,目前不重要,先删去从简
  2. // 声明并实现一个block
  3. // void (*block)(void) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  4. block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
  5. // 调用执行block
  6. // ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  7. block->FuncPtr(block);
  8. // __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
复制代码
这些穿插了许多下划线的符号现实上是差别的结构体变量,Block本质就是struct __main_block_impl_0类型的结构体,下图清楚地说明了block的底层结构:

__main_block_impl_0可以直接转换为__block_impl类型,是由于两个类型的结构体地址是一样的(相当于直接把__block_impl里的值都放到__main_block_impl_0里)
所以block.impl->FuncPtr(block)就相当于block->FuncPtr(block)
Block捕获变量原理

为了包管block内部能够正常访问外部的变量,block有个变量捕获机制
捕获局部变量(auto、static)

auto:自动变量,离开作用域就自动销毁,只存在于局部变量
static:静态局部变量
  1. // 不加关键字默认是auto变量
  2. /*auto*/ int age = 10;
  3. static int height = 175;
  4. void (^block)(void) = ^{
  5.     // age、height的值捕获进来(capture))
  6.     NSLog(@"age is %d, height is %d", age, height);
  7. };
  8. // 修改局部变量的值
  9. age = 20;
  10. height = 180;
  11. block();
  12. NSLog(@"%d %d", age, height);
复制代码
打印结果:

可以看到age仍为修改前的值,而height确确实实被修改了
将以上代码转换成C++代码来看一下:
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   int age;
  5.   int *height;
  6.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
  7.     impl.isa = &_NSConcreteStackBlock;
  8.     impl.Flags = flags;
  9.     impl.FuncPtr = fp;
  10.     Desc = desc;
  11.   }
  12. };
复制代码


  • Block结构体的变量多了两个,分别是age、height,这说明外部的变量被捕获到了Block的内部
  • 构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来生存
声明实现Block调用析构函数:
  1. int age = 10;
  2. static int height = 175;
  3. block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
  4. age = 20;
  5. height = 180;
复制代码
而后调用Block,现实调用__main_block_func_0:
  1. block->FunPtr(block)
复制代码
  1. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  2.   int age = __cself->age; // bound by copy
  3.   int *height = __cself->height; // bound by copy
  4.   NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
  5. }
复制代码
此时的age是值传递,打印的只是Block初始化时传进去的,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址不绝所指向那块内存的值
全局变量

  1. int age_ = 10;
  2. static int height_ = 175;
  3. int main(int argc, const char * argv[]) {
  4.     @autoreleasepool {
  5.         
  6.         void (^block)(void) = ^{
  7.             NSLog(@"age_ is %d, height_ is %d", age_, height_);
  8.         };
  9.         age_ = 20;
  10.         height_ = 180;
  11.         
  12.         block();
  13.       
  14.     }
  15.     return 0;
  16. }
复制代码
全局变量不绝在内存中,打印的不绝是最新的值,不用捕获

为什么会有这样的差别呢?
auto和static:由于作用域的题目,自动变量的内存随时大概被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它不绝在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值
全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),利用的时候再从Block中取出,而全局变量是直接访问
捕获实例self

  1. - (void)testSelf {
  2.     void (^block)(void) = ^{
  3.     // NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);
  4.         NSLog(@"--------%p", self);
  5.         /*
  6.         NSLog(@"--------%p", self->_name);
  7.         相当于NSLog(@"--------%p", _name);
  8.         也会捕获进去
  9.         */
  10.     };
  11.     block();
  12. }
复制代码
看了它的C++实现后,发现self也会被捕获进去
现实上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd:
  1. void testSelf(Person* self, SEL _cmd, ) {
  2.         // ...
  3. }
复制代码
即然self是参数,参数也是局部变量,它被捕获进Block也就能表明得通了
Block类型

上面提到Block是OC对象,由于它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?
首先运行以下代码:
  1. void (^block)(void) = ^{
  2.     NSLog(@"Hello!");
  3. };
  4. NSLog(@"%@ %@", block, [block class]);
  5. NSLog(@"%@", [[block class] superclass]);
  6. NSLog(@"%@", [[[block class] superclass] superclass]);
  7. /*
  8. __NSGlobalBlock__
  9. NSBlock
  10. NSObject
  11. */
复制代码
可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象
差别操作对应的Block类型差别
  1. // Global:没有访问auto变量,跟static变量无关
  2. void (^block1)(void) = ^{
  3.           NSLog(@"Hello");
  4. };
  5. // 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
  6. // 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖
  7. // Stack:访问了auto变量
  8. int age = 21;
  9. void (^block2)(void) = ^{
  10.     NSLog(@"Hello - %d", age);
  11. };
  12. // ARC下打印Malloc?MRC下确实是Stack
  13. NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
  14.     NSLog(@"%d", age);
  15. } class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__
  16. // 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
  17. // 首先肯定以运行时的结果为准,Block确实有三种类型,可能会通过Runtime动态修改类型
复制代码


  • 没有访问自动变量的Block类型是__NSGlobalBlock__,存储在数据段
    实在Global不常用,既然不访问变量,那么将代码块封装成函数一行直接调用才显得更为简洁
  • 访问了自动变量的Block类型是__NSStackBlock__,存储在栈区
    以上代码是在MRC下运行的
  • __NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__,存储在堆区
    若是在ARC下运行,纵然不用copy修饰编译器也会自动对__NSStackBlock__举行copy操作,block2的类型将会是Malloc类型
手动对每种类型的Block调用copy后的结果如下图所示

Block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
放到堆上的目的是方便我们来控制他的生命周期,可以更有效的举行内存管理
Block作为返回值

  1. typedef void(^BBlock)(void);
  2. BBlock myBlock(void) {
  3.     int age = 21;
  4.     return ^{
  5.         NSLog(@"----------%d", age);
  6.     };
  7. }
  8. BBlock bblock = myBlock();
  9. bblock();
  10. NSLog(@"%@", [bblock class]); // __NSMallocBlock__
  11. //BBlock myBlock(void) {
  12. //    return [^{
  13. //        NSLog(@"----------");
  14. //    } copy];
  15. //}
复制代码
由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很伤害,假如在MRC下运行上述代码,编译器会提示报错:

ARC下不必担心此题目,编译器会自动对返回的Block举行copy操作(如注释所写),返回拷贝到堆上的Block
将Block赋值给__strong指针

  1. int age = 21;
  2. /*__strong*/ BBlock bblock = ^{
  3.     NSLog(@"--------%d", age);
  4. };
  5. NSLog(@"%@", [bblock class]);  // ARC:__NSMallocBlock__
  6. // 没有被强指针指着
  7. NSLog(@"%@", [^{
  8.     NSLog(@"--------%d", age);
  9. } class]); // __NSStackBlock__
复制代码
Block作为Cocoa API中方法名含有usingBlock的方法参数

  1. NSArray* array = @[@"one", @2, @{@"seven" : @7}];
  2. // 遍历数组并调用Block
  3. [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  4.     NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
  5. }];
复制代码

Block作为GCD API的方法参数

  1. static dispatch_once_t onceToken;
  2. dispatch_once(&onceToken, ^{
  3.   
  4. });
  5. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  6.   
  7. });
复制代码
Block属性的写法

由于编译器会自动视情况举行copy操作,所以两种写法都没题目,只是为了统一规范建议利用copy来修饰属性
  1. @property (strong, nonatomic) void (^block)(void);
  2. @property (copy, nonatomic) void (^block)(void);
复制代码
Block访问对象类型的auto变量

Block在栈上

只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用
Block被拷贝到堆上

假如Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作
  1. BBlock bblock;
  2. {
  3.     __strong Person* person = [[Person alloc] init];
  4.     // __weak Person* person = [[Person alloc] init];
  5.     person.age = 21;
  6.     bblock = ^{
  7.         // 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
  8.         NSLog(@"-%d-", person.age);
  9.     };
  10.    
  11.     // MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
  12.     //[bblock release];
  13. }
  14. NSLog(@"--------------");
复制代码
将上面代码文件转换成C++文件:
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   Person *__strong person;
  5.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
  6.     impl.isa = &_NSConcreteStackBlock;
  7.     impl.Flags = flags;
  8.     impl.FuncPtr = fp;
  9.     Desc = desc;
  10.   }
  11. };
  12. static struct __main_block_desc_0 {
  13.   size_t reserved;
  14.   size_t Block_size;
  15.   void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  16.   void (*dispose)(struct __main_block_impl_0*);
  17. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  18. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
复制代码
Block内部的__main_block_desc_0结构领会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
Block从堆上移除

假如Block从堆上移除,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动release引用的auto变量
  1. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
复制代码
注:


  • 只有在引用对象类型的变量时,才会天生copy和dispose函数
  • 假如引用的是static修饰的对象类型,那么捕获的变量在C++代码中将会是Person *__strong *person;
  • 代码里有__weak,转换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
利用GCD API验证Block对外部变量的强弱引用(Github Demo):
  1. - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
  2.     Person* person = [[Person alloc] init];
  3.    
  4.     __weak Person* weakPerson = person;
  5.    
  6.     // 强引用了,Block调用完毕释放了person才会释放
  7. //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  8. //        NSLog(@"---%@", person);
  9. //    });
  10.    
  11.     // 弱引用,调用Block之前person已经释放
  12. //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  13. //        NSLog(@"---%@", weakPerson);
  14. //    });
  15.    
  16.     // 编译器已经检查到会有强引用
  17. //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  18. //        NSLog(@"---1%@", weakPerson);
  19. //        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  20. //            NSLog(@"---2%@", person);
  21. //        });
  22. //    });
  23.    
  24.     // 不会等到弱引用就释放了
  25.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  26.         NSLog(@"---1%@", person);
  27.         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  28.             NSLog(@"---2%@", weakPerson);
  29.         });
  30.     });
  31.    
  32.     NSLog(@"Screen Touched");
  33. }
复制代码
修饰符__block

假如在Block内部修改捕获的auto变量值,编译器将会报错:
  1. int age = 21;
  2. BBlock block = ^{
  3.     age = 20;
  4.     NSLog(@"%d", age);
  5. };
  6. block();
复制代码

从底层可看出在这里修改变量的值,现实上是通过改变__main_block_fun_0函数里的局部变量到达改变main函数里的变量,这是两个独立的函数,显然不大概
1. 利用static修饰变量
用static来修饰age属性,底层用指针访问,block内部引用的是age的地址值,函数间会传递变量的地址,可以根据地址去修改age的值,修改的就是同一块内存
但欠好的是age属性会不绝存放在内存中不销毁,造成多余的内存占用,而且会改变age属性的性质,不再是一个auto变量了
2. 利用__block修饰变量
用__block来修饰属性,底层会天生__Block_byref_age_0类型的结构体对象,里面存储着age的真实值

转换成C++文件来检察内部结构,经__block修饰后,会根据__main_block_impl_0里天生的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址,目的就是不须要知道内部的真实实现,所看到的就是打印出来的值
  1. struct __Block_byref_age_0 {
  2.   void *__isa;
  3. __Block_byref_age_0 *__forwarding;  // 指向结构体本身
  4. int __flags;
  5. int __size;
  6. int age;
  7. };
  8. struct __main_block_impl_0 {
  9.   struct __block_impl impl;
  10.   struct __main_block_desc_0* Desc;
  11.   __Block_byref_age_0 *age; // by ref
  12.   
  13.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
  14.     impl.isa = &_NSConcreteStackBlock;
  15.     impl.Flags = flags;
  16.     impl.FuncPtr = fp;
  17.     Desc = desc;
  18.   }
  19. };
  20. static struct __main_block_desc_0 {
  21.   size_t reserved;
  22.   size_t Block_size;
  23.   void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  24.   void (*dispose)(struct __main_block_impl_0*);
  25. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  26. int main(int argc, const char * argv[]) {
  27.     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  28.                
  29.                 // 传进去的是age的地址
  30.         __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
  31.         
  32.         Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
  33.         ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  34.     }
  35.     return 0;
  36. }
复制代码
总结


  • __block可以用于解决block内部无法修改auto变量值的题目
  • 编译器会将__block变量包装成一个对象
  • 实在修改的变量是__block天生的对象里面存储的变量的值,而不是外面的auto变量,但是内部天生的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量
  • __block不能修饰全局变量、静态变量(static)
__block内存管理

步伐编译时,block和__block都是在栈中的,这时并不会对__block变量产生强引用
由于__block也会包装成 OC对象,所以block底层也会天生copy函数和dispose函数
  1. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  2.     _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
  3. static void __main_block_dispose_0(struct __main_block_impl_0*src) {
  4.     _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
复制代码
Block复制到堆上
当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)
现实上,这时__block修饰的变量由于被包装成了OC对象,所以也会被拷贝到堆上,假如再有block强引用__block,由于__block变量已经拷贝到堆上了,就不会再拷贝了

Block从堆上移除
当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)
假如有多个block同时持有着__block变量,那么只有所有的block都从堆中移除了,__block变量才会被释放

__block和OC对象在block中的区别
__block天生的对象就是强引用,而NSObject对象会根据修饰符__strong或者__weak来区分是否要举行retain操作
注意:__weak不能修饰基本数据类型,编译器会报__weak' only applies to Objective-C object or block pointer types; type here is 'int'警告
__forwarding指针

  1. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  2.   __Block_byref_age_0 *age = __cself->age; // bound by ref
  3.   (age->__forwarding->age) = 20;
  4. }
复制代码


  • 在栈中,__block中的__forwarding指针指向自己的内存地址
  • 复制到堆中之后,__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block
  • 这样的目的都是为了不论访问的__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量

包管20被存储在堆中Block所引用的变量
__block修饰对象类型

情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong或__weak修饰
当__block对象在栈上时,不会对指向的对象产生强引用
当__block对象被copy到堆上时,也会天生一个新的结构体对象,并且只会被block举行强引用,会根据差别的修饰符__strong和__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用
  1. struct __Block_byref_weakPerson_0 {
  2.         void __isa;
  3.         __Block_byref_weakPerson_0 __forwarding;
  4.         int __flags;
  5.         int __size;
  6.         void (__Block_byref_id_object_copy)(void, void);
  7.         void (__Block_byref_id_object_dispose)(void*);
  8.         Person *__weak weakPerson;
  9. };
  10. static void __Block_byref_id_object_copy_131(void *dst, void src) {
  11. _Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
  12. }
  13. static void __Block_byref_id_object_dispose_131(void src) {
  14. _Block_object_dispose((void * ) ((char)src + 40), 131);
  15. // __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
  16. __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
复制代码
注:在MRC环境下纵然用__block修饰,对于结构体对象的成员变量,__block内部只会对auto变量举行弱引用,无论加不加__weak,block还没有释放,__block修饰的变量就已经释放了,这点和在ARC环境下差别
Block循环引用

两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放
  1. int main(int argc, const char * argv[]) {
  2.     @autoreleasepool {
  3.         Person* person = [[Person alloc] init];
  4.         person.age = 21;
  5.         person.block = ^{
  6.             NSLog(@"%d", person.age);
  7.         };
  8.     }
  9.     NSLog(@"111111111111");
  10.     return 0;
  11. }
复制代码
结果就是person对象不会释放,由于没有调用dealloc方法
person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放
  1. @implementation Person
  2. - (void)test {
  3.     self.block = ^{
  4.         NSLog(@"%d", self.age);
  5.     };
  6. }
  7. - (void)dealloc
  8. {
  9.     NSLog(@"%s", __func__);
  10. }
  11. @end
  12. int main(int argc, const char * argv[]) {
  13.     @autoreleasepool {
  14.         Person* person = [[Person alloc] init];
  15.         person.age = 21;
  16.         [person test];
  17.     }
  18.     return 0;
  19.     NSLog(@"111111111111");
  20. }
复制代码
block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)self,self又持有block,同样会造成循环引用

解决办法



  • 利用__weak、__unsafe_unretained让Block指向对象的引用变为弱引用

    1. //    __unsafe_unretained typeof(self)weakSelf = self;
    2. __weak typeof(self)weakSelf = self;
    3.   
    4. self.block = ^{
    5.     NSLog(@"%d", weakSelf.age);
    6. };
    复制代码
  • 用__block解决,用__block修饰对象会造成三者相互引用造成循环引用,须要手动调用block

    1. __block Person* person = [[Person alloc] init];
    2. person.age = 21;
    3. person.block = ^{
    4.     NSLog(@"%d", person.age);
    5.     person = nil;
    6. };
    7. person.block();
    复制代码
    block内部也须要手动将person置空,这个person是__block内部天生的指向Person对象的变量
  • block传参,将self作为参数传入block中,举行指针拷贝,并没有对self举行持有
    1. // Person.m
    2. self.block = ^(Person * _Nonnull person) {
    3.     NSLog(@"%d", person.age);
    4. };
    5. self.block(self);
    复制代码
  • MRC下不支持__weak,只能利用__unsafe_unretained
    MRC下直接利用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:
    1. __block Person *person = [[Person alloc] init];
    2. person.age = 10;
    3. person.block = [^{
    4.         NSLog(@"age is %d", person.age);
    5. } copy];
    6. [person release];
    复制代码
强弱共舞

这种情况虽没有引起循环引用,但block耽误执行2秒,等person释放后,就无法获取其age,很不合理
  1. __weak typeof(person) weakPerson = person;
  2. person.block = ^{
  3.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  4.         NSLog(@"%d", weakPerson.age);
  5.     });
  6. };
  7. person.block();
复制代码
改进一下:
  1. __weak typeof(person) weakPerson = person;
  2. person.block = ^{
  3.     __strong __typeof(weakPerson)strongPerson = weakPerson;
  4.    
  5.     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  6.         NSLog(@"%d", strongPerson.age);
  7.     });
  8. };
  9. person.block();
复制代码
通过运行结果发现,完全解决了以上self中途被释放的题目,这是为什么呢?分析如下:


  • 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self
  • weakSelf被强引用了就不会自动释放,由于strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放
总结

Block在iOS开发中极为重要,非常适合处理异步操作、回调、聚集操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表