目录
前言
Block
是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,因为它内部有isa
指针
Block的基本使用请看这两篇文章:
- k
- l
本篇文章着重探究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
:
block->FunPtr(block)
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的内存管理、变量捕获和循环引用解决方案