媒介
Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,由于它内部有isa指针
Block的基本利用请看这两篇文章:
本篇文章偏重探究Block这些特性的底层原理
Block底层结构
声明一个最简单的块并调用:
- void (^block)(void) = ^{
- NSLog(@"Hello World!");
- };
- block();
复制代码 利用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:
- // 原本的代码有各种强制转换,目前不重要,先删去从简
- // 声明并实现一个block
- // void (*block)(void) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
- block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
- // 调用执行block
- // ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
- block->FuncPtr(block);
- // __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:静态局部变量
- // 不加关键字默认是auto变量
- /*auto*/ int age = 10;
- static int height = 175;
- void (^block)(void) = ^{
- // age、height的值捕获进来(capture))
- NSLog(@"age is %d, height is %d", age, height);
- };
- // 修改局部变量的值
- age = 20;
- height = 180;
- block();
- NSLog(@"%d %d", age, height);
复制代码 打印结果:
可以看到age仍为修改前的值,而height确确实实被修改了
将以上代码转换成C++代码来看一下:
- struct __main_block_impl_0 {
- struct __block_impl impl;
- struct __main_block_desc_0* Desc;
- int age;
- int *height;
- __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
- impl.isa = &_NSConcreteStackBlock;
- impl.Flags = flags;
- impl.FuncPtr = fp;
- Desc = desc;
- }
- };
复制代码
- Block结构体的变量多了两个,分别是age、height,这说明外部的变量被捕获到了Block的内部
- 构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来生存
声明实现Block调用析构函数:
- int age = 10;
- static int height = 175;
- block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
- age = 20;
- height = 180;
复制代码 而后调用Block,现实调用__main_block_func_0:
- static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
- int age = __cself->age; // bound by copy
- int *height = __cself->height; // bound by copy
- NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
- }
复制代码 此时的age是值传递,打印的只是Block初始化时传进去的值,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址不绝所指向那块内存的值
全局变量
- int age_ = 10;
- static int height_ = 175;
- int main(int argc, const char * argv[]) {
- @autoreleasepool {
-
- void (^block)(void) = ^{
- NSLog(@"age_ is %d, height_ is %d", age_, height_);
- };
- age_ = 20;
- height_ = 180;
-
- block();
-
- }
- return 0;
- }
复制代码 全局变量不绝在内存中,打印的不绝是最新的值,不用捕获
为什么会有这样的差别呢?
auto和static:由于作用域的题目,自动变量的内存随时大概被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它不绝在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值
全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),利用的时候再从Block中取出,而全局变量是直接访问
捕获实例self
- - (void)testSelf {
- void (^block)(void) = ^{
- // NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);
- NSLog(@"--------%p", self);
- /*
- NSLog(@"--------%p", self->_name);
- 相当于NSLog(@"--------%p", _name);
- 也会捕获进去
- */
- };
- block();
- }
复制代码 看了它的C++实现后,发现self也会被捕获进去
现实上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd:
- void testSelf(Person* self, SEL _cmd, ) {
- // ...
- }
复制代码 即然self是参数,参数也是局部变量,它被捕获进Block也就能表明得通了
Block类型
上面提到Block是OC对象,由于它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?
首先运行以下代码:
- void (^block)(void) = ^{
- NSLog(@"Hello!");
- };
- NSLog(@"%@ %@", block, [block class]);
- NSLog(@"%@", [[block class] superclass]);
- NSLog(@"%@", [[[block class] superclass] superclass]);
- /*
- __NSGlobalBlock__
- NSBlock
- NSObject
- */
复制代码 可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象
差别操作对应的Block类型差别
- // Global:没有访问auto变量,跟static变量无关
- void (^block1)(void) = ^{
- NSLog(@"Hello");
- };
- // 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
- // 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖
- // Stack:访问了auto变量
- int age = 21;
- void (^block2)(void) = ^{
- NSLog(@"Hello - %d", age);
- };
- // ARC下打印Malloc?MRC下确实是Stack
- NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
- NSLog(@"%d", age);
- } class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__
- // 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
- // 首先肯定以运行时的结果为准,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作为返回值
- typedef void(^BBlock)(void);
- BBlock myBlock(void) {
- int age = 21;
- return ^{
- NSLog(@"----------%d", age);
- };
- }
- BBlock bblock = myBlock();
- bblock();
- NSLog(@"%@", [bblock class]); // __NSMallocBlock__
- //BBlock myBlock(void) {
- // return [^{
- // NSLog(@"----------");
- // } copy];
- //}
复制代码 由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很伤害,假如在MRC下运行上述代码,编译器会提示报错:
ARC下不必担心此题目,编译器会自动对返回的Block举行copy操作(如注释所写),返回拷贝到堆上的Block
将Block赋值给__strong指针
- int age = 21;
- /*__strong*/ BBlock bblock = ^{
- NSLog(@"--------%d", age);
- };
- NSLog(@"%@", [bblock class]); // ARC:__NSMallocBlock__
- // 没有被强指针指着
- NSLog(@"%@", [^{
- NSLog(@"--------%d", age);
- } class]); // __NSStackBlock__
复制代码 Block作为Cocoa API中方法名含有usingBlock的方法参数
- NSArray* array = @[@"one", @2, @{@"seven" : @7}];
- // 遍历数组并调用Block
- [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
- }];
复制代码
Block作为GCD API的方法参数
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
-
- });
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
-
- });
复制代码 Block属性的写法
由于编译器会自动视情况举行copy操作,所以两种写法都没题目,只是为了统一规范建议利用copy来修饰属性
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
复制代码 Block访问对象类型的auto变量
Block在栈上
只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用
Block被拷贝到堆上
假如Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作
- BBlock bblock;
- {
- __strong Person* person = [[Person alloc] init];
- // __weak Person* person = [[Person alloc] init];
- person.age = 21;
- bblock = ^{
- // 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
- NSLog(@"-%d-", person.age);
- };
-
- // MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
- //[bblock release];
- }
- NSLog(@"--------------");
复制代码 将上面代码文件转换成C++文件:
- 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;
- }
- };
- 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};
- 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变量
- 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):
- - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
- Person* person = [[Person alloc] init];
-
- __weak Person* weakPerson = person;
-
- // 强引用了,Block调用完毕释放了person才会释放
- // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // NSLog(@"---%@", person);
- // });
-
- // 弱引用,调用Block之前person已经释放
- // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // NSLog(@"---%@", weakPerson);
- // });
-
- // 编译器已经检查到会有强引用
- // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // NSLog(@"---1%@", weakPerson);
- // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // NSLog(@"---2%@", person);
- // });
- // });
-
- // 不会等到弱引用就释放了
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"---1%@", person);
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"---2%@", weakPerson);
- });
- });
-
- NSLog(@"Screen Touched");
- }
复制代码 修饰符__block
假如在Block内部修改捕获的auto变量值,编译器将会报错:
- int age = 21;
- BBlock block = ^{
- age = 20;
- NSLog(@"%d", age);
- };
- 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的地址,目的就是不须要知道内部的真实实现,所看到的就是打印出来的值
- struct __Block_byref_age_0 {
- void *__isa;
- __Block_byref_age_0 *__forwarding; // 指向结构体本身
- int __flags;
- int __size;
- int age;
- };
- struct __main_block_impl_0 {
- struct __block_impl impl;
- struct __main_block_desc_0* Desc;
- __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;
- }
- };
- 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};
- int main(int argc, const char * argv[]) {
- /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
-
- // 传进去的是age的地址
- __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
-
- Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
- ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
- }
- return 0;
- }
复制代码 总结
- __block可以用于解决block内部无法修改auto变量值的题目
- 编译器会将__block变量包装成一个对象
- 实在修改的变量是__block天生的对象里面存储的变量的值,而不是外面的auto变量,但是内部天生的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量
- __block不能修饰全局变量、静态变量(static)
__block内存管理
步伐编译时,block和__block都是在栈中的,这时并不会对__block变量产生强引用
由于__block也会包装成 OC对象,所以block底层也会天生copy函数和dispose函数
- static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
- _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
- static void __main_block_dispose_0(struct __main_block_impl_0*src) {
- _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指针
- 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;
- }
复制代码
- 在栈中,__block中的__forwarding指针指向自己的内存地址
- 复制到堆中之后,__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block
- 这样的目的都是为了不论访问的__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量
包管20被存储在堆中Block所引用的变量
__block修饰对象类型
情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong或__weak修饰
当__block对象在栈上时,不会对指向的对象产生强引用
当__block对象被copy到堆上时,也会天生一个新的结构体对象,并且只会被block举行强引用,会根据差别的修饰符__strong和__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用
- struct __Block_byref_weakPerson_0 {
- void __isa;
- __Block_byref_weakPerson_0 __forwarding;
- int __flags;
- int __size;
- void (__Block_byref_id_object_copy)(void, void);
- void (__Block_byref_id_object_dispose)(void*);
- Person *__weak weakPerson;
- };
- static void __Block_byref_id_object_copy_131(void *dst, void src) {
- _Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
- }
- static void __Block_byref_id_object_dispose_131(void src) {
- _Block_object_dispose((void * ) ((char)src + 40), 131);
- // __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};
- __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循环引用
两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放
- int main(int argc, const char * argv[]) {
- @autoreleasepool {
- Person* person = [[Person alloc] init];
- person.age = 21;
- person.block = ^{
- NSLog(@"%d", person.age);
- };
- }
- NSLog(@"111111111111");
- return 0;
- }
复制代码 结果就是person对象不会释放,由于没有调用dealloc方法
person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放
- @implementation Person
- - (void)test {
- self.block = ^{
- NSLog(@"%d", self.age);
- };
- }
- - (void)dealloc
- {
- NSLog(@"%s", __func__);
- }
- @end
- int main(int argc, const char * argv[]) {
- @autoreleasepool {
- Person* person = [[Person alloc] init];
- person.age = 21;
- [person test];
- }
- return 0;
- NSLog(@"111111111111");
- }
复制代码 block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)self,self又持有block,同样会造成循环引用
解决办法
- 利用__weak、__unsafe_unretained让Block指向对象的引用变为弱引用
- // __unsafe_unretained typeof(self)weakSelf = self;
- __weak typeof(self)weakSelf = self;
-
- self.block = ^{
- NSLog(@"%d", weakSelf.age);
- };
复制代码 - 用__block解决,用__block修饰对象会造成三者相互引用造成循环引用,须要手动调用block
- __block Person* person = [[Person alloc] init];
- person.age = 21;
- person.block = ^{
- NSLog(@"%d", person.age);
- person = nil;
- };
- person.block();
复制代码 block内部也须要手动将person置空,这个person是__block内部天生的指向Person对象的变量
- block传参,将self作为参数传入block中,举行指针拷贝,并没有对self举行持有
- // Person.m
- self.block = ^(Person * _Nonnull person) {
- NSLog(@"%d", person.age);
- };
- self.block(self);
复制代码 - MRC下不支持__weak,只能利用__unsafe_unretained
MRC下直接利用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:
- __block Person *person = [[Person alloc] init];
- person.age = 10;
- person.block = [^{
- NSLog(@"age is %d", person.age);
- } copy];
- [person release];
复制代码 强弱共舞
这种情况虽没有引起循环引用,但block耽误执行2秒,等person释放后,就无法获取其age,很不合理
- __weak typeof(person) weakPerson = person;
- person.block = ^{
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"%d", weakPerson.age);
- });
- };
- person.block();
复制代码 改进一下:
- __weak typeof(person) weakPerson = person;
- person.block = ^{
- __strong __typeof(weakPerson)strongPerson = weakPerson;
-
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- NSLog(@"%d", strongPerson.age);
- });
- };
- person.block();
复制代码 通过运行结果发现,完全解决了以上self中途被释放的题目,这是为什么呢?分析如下:
- 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self
- weakSelf被强引用了就不会自动释放,由于strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放
总结
Block在iOS开发中极为重要,非常适合处理异步操作、回调、聚集操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |