Block的本质
Block是将函数及其执行上下文封装起来的对象。
Block调用即是函数的调用。
#import "CLBlock.h"
@implementation CLBlock
- (void)blockTest {
int multiplier = 6;
int(^BlockTest)(int) = ^int(int num){
return num * multiplier;
};
BlockTest(2);
}
@end
clang(LLVM编译器)具有转换为我们可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。
使用【clang -rewrite-objc CLBlock.m】进行源码解析,查看编译后的文件内容。
// @implementation CLBlock
//Block结构体
struct __CLBlock__blockTest_block_impl_0 {
struct __block_impl impl;
struct __CLBlock__blockTest_block_desc_0* Desc;
int multiplier;
//构造函数
__CLBlock__blockTest_block_impl_0(void *fp, struct __CLBlock__blockTest_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock;//isa指针,Block是对象的标志
impl.Flags = flags;
impl.FuncPtr = fp;//函数指针
Desc = desc;
}
};
/**函数
第一个参数:Block结构体
第二个参数:传入参数
*/
static int __CLBlock__blockTest_block_func_0(struct __CLBlock__blockTest_block_impl_0 *__cself, int num) {
int multiplier = __cself->multiplier; // bound by copy
return num * multiplier;
}
static struct __CLBlock__blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __CLBlock__blockTest_block_desc_0_DATA = { 0, sizeof(struct __CLBlock__blockTest_block_impl_0)};
static void _I_CLBlock_blockTest(CLBlock * self, SEL _cmd) {
int multiplier = 6;
/*
int(^BlockTest)(int) = ^int(int num){
return num * multiplier;
};
*/
int(*BlockTest)(int) = ((int (*)(int))&__CLBlock__blockTest_block_impl_0((void *)__CLBlock__blockTest_block_func_0, &__CLBlock__blockTest_block_desc_0_DATA, multiplier));
/*
BlockTest(2);
*/
((int (*)(__block_impl *, int))((__block_impl *)BlockTest)->FuncPtr)((__block_impl *)BlockTest, 2);
}
// @end
struct __block_impl {
void *isa;//isa指针,Block是对象的标志
int Flags; //标志变量,在实现block的内部操作时会用到
int Reserved;//保留变量
void *FuncPtr;//函数指针
};
通过编译后的源码得知,block编译后为一个含有isa指针的结构体,所以可以将block当做对象;而block的上下文内容被编译后一个函数。而调用时便是将编译后的上下文函数作为参数使用。
static struct __CLBlock__blockTest_block_desc_0 {
size_t reserved;//保留字段
size_t Block_size;//block大小(sizeof(struct __main_block_impl_0))
} __CLBlock__blockTest_block_desc_0_DATA = { 0, sizeof(struct __CLBlock__blockTest_block_impl_0)};
在定义__CLBlock__blockTest_block_desc_0结构体时,同时创建了__CLBlock__blockTest_block_desc_0_DATA,并给它赋值,以供在blockTest函数中对__CLBlock__blockTest_block_impl_0进行初始化。
截获变量
为了保证 block 内部能够正常访问外部的变量,block 有一个变量捕获机制。
- 对于基本数据类型的局部变量截获其值。
- 对于对象类型的局部变量连同所有权修饰符一起截获。
- 以指针形式截获局部静态变量。
- 不截获全局变量、静态全局变量
#import "CLBlock.h"
#import "Test.h"
@implementation CLBlock
int global_var = 4;//全局变量
static int static_global_var = 5;//静态全局变量
- (void)blockTest {
//基本数据类型的局部变量
int var = 6;
//对象类型的局部变量
__unsafe_unretained id unsafe_objc = nil;
__strong id strong_obj = nil;
Test *test = nil;
//静态局部变量
static int static_var = 7;
void(^BlockTest)(void) = ^{
NSLog(@"global_var==%d",global_var);
NSLog(@"static_global_var==%d",static_global_var);
NSLog(@"var==%d",var);
NSLog(@"unsafe_objc==%@",unsafe_objc);
NSLog(@"strong_obj==%@",strong_obj);
NSLog(@"test==%@",test);
NSLog(@"static_var==%d",static_var);
};
global_var = 41;
static_global_var = 51;
var = 61;
unsafe_objc = [[NSObject alloc] init];
strong_obj = [[NSObject alloc] init];
test = [[Test alloc] init];
static_var = 71;
BlockTest();
}
@end
运行结果
global_var==41
static_global_var==51
var==6
unsafe_objc==(null)
strong_obj==(null)
test==(null)
static_var==71
使用【clang -rewrite-objc -fobjc-arc CLBlock.m】命令进行源码解析
int global_var = 4;
static int static_global_var = 5;
struct __CLBlock__blockTest_block_impl_0 {
struct __block_impl impl;
struct __CLBlock__blockTest_block_desc_0* Desc;
//截获基本数据类型的局部变量的值
int var;
//对象类型的局部变量,其值连同所有权修饰符一起截获
__unsafe_unretained id unsafe_objc;
__strong id strong_obj;
Test *__strong test;
//以指针形式截获静态局部变量
int *static_var;
//对全局变量、静态全局变量不截获
__CLBlock__blockTest_block_impl_0(void *fp, struct __CLBlock__blockTest_block_desc_0 *desc, int _var, __unsafe_unretained id _unsafe_objc, __strong id _strong_obj, Test *__strong _test, int *_static_var, int flags=0) : var(_var), unsafe_objc(_unsafe_objc), strong_obj(_strong_obj), test(_test), static_var(_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __CLBlock__blockTest_block_func_0(struct __CLBlock__blockTest_block_impl_0 *__cself) {
int var = __cself->var; // bound by copy
id unsafe_objc = __cself->unsafe_objc; // bound by copy
id strong_obj = __cself->strong_obj; // bound by copy
Test *test = __cself->test; // bound by copy
int *static_var = __cself->static_var; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_0,global_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_1,static_global_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_2,var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_3,unsafe_objc);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_4,strong_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_5,test);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_by_brpygpw16r5cz_vx4d7_prcw0000gn_T_CLBlock_a61b60_mi_6,(*static_var));
}
static void __CLBlock__blockTest_block_copy_0(struct __CLBlock__blockTest_block_impl_0*dst, struct __CLBlock__blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->unsafe_objc, (void*)src->unsafe_objc, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->strong_obj, (void*)src->strong_obj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->test, (void*)src->test, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __CLBlock__blockTest_block_dispose_0(struct __CLBlock__blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->unsafe_objc, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->strong_obj, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->test, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __CLBlock__blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __CLBlock__blockTest_block_impl_0*, struct __CLBlock__blockTest_block_impl_0*);
void (*dispose)(struct __CLBlock__blockTest_block_impl_0*);
} __CLBlock__blockTest_block_desc_0_DATA = { 0, sizeof(struct __CLBlock__blockTest_block_impl_0), __CLBlock__blockTest_block_copy_0, __CLBlock__blockTest_block_dispose_0};
static void _I_CLBlock_blockTest(CLBlock * self, SEL _cmd) {
int var = 6;
__attribute__((objc_ownership(none))) id unsafe_objc = __null;
__attribute__((objc_ownership(strong))) id strong_obj = __null;
Test *test = __null;
static int static_var = 7;
void(*BlockTest)(void) = ((void (*)())&__CLBlock__blockTest_block_impl_0((void *)__CLBlock__blockTest_block_func_0, &__CLBlock__blockTest_block_desc_0_DATA, var, unsafe_objc, strong_obj, test, &static_var, 570425344));
global_var = 41;
static_global_var = 51;
var = 61;
unsafe_objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
strong_obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
test = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"));
static_var = 71;
((void (*)(__block_impl *))((__block_impl *)BlockTest)->FuncPtr)((__block_impl *)BlockTest);
}
__block修饰符
对被截获的局部变量进行赋值操作需要__block修饰符;
对于静态局部变量、全局变量、静态全局变量不需要__block修饰符;
赋值≠使用
NSMutableArray *mArray = [[NSMutableArray alloc] init];
void(^Block)(void) = ^{
[mArray addObject:@123];
};
Block();
此种情况不需要__block修饰符。
__block原理
__block修饰的变量变成了对象。
示例:
#import "CLBlock.h"
#import "Test.h"
@implementation CLBlock
int global_var = 4;//全局变量
static int static_global_var = 5;//静态全局变量
- (void)blockTest {
//基本数据类型的局部变量
__block int var = 6;
//对象类型的局部变量
__block __unsafe_unretained id unsafe_objc = nil;
__block __strong id strong_obj = nil;
__block Test *test = nil;
//静态局部变量
static int static_var = 7;
void(^BlockTest)(void) = ^{
NSLog(@"global_var==%d",global_var);
NSLog(@"static_global_var==%d",static_global_var);
NSLog(@"var==%d",var);
NSLog(@"unsafe_objc==%@",unsafe_objc);
NSLog(@"strong_obj==%@",strong_obj);
NSLog(@"test==%@",test);
NSLog(@"static_var==%d",static_var);
};
global_var = 41;
static_global_var = 51;
var = 61;
unsafe_objc = [[NSObject alloc] init];
strong_obj = [[NSObject alloc] init];
test = [[Test alloc] init];
static_var = 71;
BlockTest();
NSMutableArray *mArray = [[NSMutableArray alloc] init];
void(^Block)(void) = ^{
[mArray addObject:@123];
};
Block();
}
@end
运行结果:
global_var==41
static_global_var==51
var==61
unsafe_objc==
strong_obj==
test==
static_var==71
查看源码编译:
int global_var = 4;
static int static_global_var = 5;
struct __Block_byref_var_0 {
void *__isa;
__Block_byref_var_0 *__forwarding;
int __flags;
int __size;
int var;
};
struct __Block_byref_unsafe_objc_1 {
void *__isa;
__Block_byref_unsafe_objc_1 *__forwarding;
int __flags;
int __size;
__unsafe_unretained id unsafe_objc;
};
struct __Block_byref_strong_obj_2 {
void *__isa;
__Block_byref_strong_obj_2 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__strong id strong_obj;
};
struct __Block_byref_test_3 {
void *__isa;
__Block_byref_test_3 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Test *__strong test;
};
struct __CLBlock__blockTest_block_impl_0 {
struct __block_impl impl;
struct __CLBlock__blockTest_block_desc_0* Desc;
int *static_var;
__Block_byref_var_0 *var; // by ref
__Block_byref_unsafe_objc_1 *unsafe_objc; // by ref
__Block_byref_strong_obj_2 *strong_obj; // by ref
__Block_byref_test_3 *test; // by ref
__CLBlock__blockTest_block_impl_0(void *fp, struct __CLBlock__blockTest_block_desc_0 *desc, int *_static_var, __Block_byref_var_0 *_var, __Block_byref_unsafe_objc_1 *_unsafe_objc, __Block_byref_strong_obj_2 *_strong_obj, __Block_byref_test_3 *_test, int flags=0) : static_var(_static_var), var(_var->__forwarding), unsafe_objc(_unsafe_objc->__forwarding), strong_obj(_strong_obj->__forwarding), test(_test->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
......
此处省略部分代码
......
static void _I_CLBlock_blockTest(CLBlock * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 6};
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(none))) __Block_byref_unsafe_objc_1 unsafe_objc = {(void*)0,(__Block_byref_unsafe_objc_1 *)&unsafe_objc, 0, sizeof(__Block_byref_unsafe_objc_1), __null};
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(strong))) __Block_byref_strong_obj_2 strong_obj = {(void*)0,(__Block_byref_strong_obj_2 *)&strong_obj, 33554432, sizeof(__Block_byref_strong_obj_2), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, __null};
__attribute__((__blocks__(byref))) __Block_byref_test_3 test = {(void*)0,(__Block_byref_test_3 *)&test, 33554432, sizeof(__Block_byref_test_3), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, __null};
static int static_var = 7;
void(*BlockTest)(void) = ((void (*)())&__CLBlock__blockTest_block_impl_0((void *)__CLBlock__blockTest_block_func_0, &__CLBlock__blockTest_block_desc_0_DATA, &static_var, (__Block_byref_var_0 *)&var, (__Block_byref_unsafe_objc_1 *)&unsafe_objc, (__Block_byref_strong_obj_2 *)&strong_obj, (__Block_byref_test_3 *)&test, 570425344));
global_var = 41;
static_global_var = 51;
(var.__forwarding->var) = 61;
(unsafe_objc.__forwarding->unsafe_objc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
(strong_obj.__forwarding->strong_obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
(test.__forwarding->test) = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"));
static_var = 71;
((void (*)(__block_impl *))((__block_impl *)BlockTest)->FuncPtr)((__block_impl *)BlockTest);
NSMutableArray *mArray = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
void(*Block)(void) = ((void (*)())&__CLBlock__blockTest_block_impl_1((void *)__CLBlock__blockTest_block_func_1, &__CLBlock__blockTest_block_desc_1_DATA, mArray, 570425344));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
以基本数据类型为例,通过与添加__block修饰符之前源码对比发现:
int
类型变为__Block_byref_var_0*
对象类型;
赋值时由var = 61;
变为(var.__forwarding->var) = 61;
Block的内存管理
根据Block在内存中的位置分为三种类型:
- 全局类型:_NSConcreteGlobalBlock,位于全局区的block,它是设置在程序的数据区域(.data区)中。
- 栈类型:_NSConcreteStackBlock,位于栈区,超出变量作用域,栈上的Block以及 __block变量都被销毁。
- 堆类型:_NSConcreteMallocBlock位于堆区,在变量作用域结束时不受影响。
_NSConcreteGlobalBlock
生成全局类型Block有两种情况
- 定义全局变量的地方有Block语法时
#import "CLBlock.h"
@implementation CLBlock
void(^BlockTest)(void) = ^{ };
- (void)blockTest {
}
@end
编译后
struct __BlockTest_block_impl_0 {
struct __block_impl impl;
struct __BlockTest_block_desc_0* Desc;
__BlockTest_block_impl_0(void *fp, struct __BlockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- Block语法不使用截获的自动变量时候
int (^BlockTest)(int num) = ^(int num){
return num;
};
BlockTest(2);
虽然,这个Block在循环内,但是Block的地址总是不变的。说明这个Block在全局段。注:针对没有捕获自动变量的Block来说,虽然用clang的rewrite-objc转化后的代码中仍显示_NSConcretStackBlock,但是实际上不是这样的。????
_NSConcreteStackBlock
设置在栈上的Block,如果其作用域结束,该Block就被销毁。同样的,由于__block变量也配置在栈上,如果其作用域结束,则该__block变量也会被销毁。
_NSConcreteMallocBlock
堆类型Block无法直接创建,需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。
- 栈Block被拷贝生成到堆情况
1.调用Block的copy实例方法时
2.Block作为函数返回值返回时
3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
4.将方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
Block的copy操作
Block由栈 copy至堆的内存使用变化
-
当栈上的Block被复制到堆上时,__block变量也会随之复制,并且Block持有该变量
-
多个Block中使用__block变量时,任何一个Block被复制到堆时,__block变量也会一并复制到堆并被持有,其余Block被复制时,仅需要__block 变量的引用计数
和使用引用计数一样,在堆上的Block被废弃,它所引用的__block变量将被释放
理解Block作用域之后,我们发现这和OC引用计数方式管理方式一样,使用__block修饰符来持有对象,当Block被废弃之后,__block修饰变量也随之释放
当Block被复制到堆之后,会将自身的__forwarding指针更新,依然指向“最新”的自己,这样就保证了在栈上或者堆上都能正确访问对应变量
__forwarding无论在任何内存位置,都可以顺利的访问同一个__block变量。
Block的循环引用
循环引用
当前对象持有block
,在block
中使用self
又造成block
持有self
,引起循环引用。
- (void)blockTest {
_block = ^{
NSLog(@"%@",self.test);
};
}
使用__weak
关键字解除循环引用:
- (void)blockTest {
__weak typeof(self)weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf.test);
};
}
__block引起的循环引用
注意:
MRC 下,__block
修改对象不会引起其引用计数,避免了循环引用。
ARC下,__block
修改对象会引起强引用,无法避免了循环引用,需要手动解环。
- (void)blockTest {
__block CLBlock *blockSelf = self;
_block = ^{
NSLog(@"%@",blockSelf.test);
};
_block();
}
对象持有Block,Block持有__block变量,__block变量持有对象,引起大环引用。
解决方式:
- (void)blockTest {
__block CLBlock *blockSelf = self;
_block = ^{
NSLog(@"%@",blockSelf.test);
blockSelf = nil;
};
_block();
}
注意:如果Block一直不调用,无法解环。
Block对对象的长引用
Block 中如果持有了self
就会对其强引用,项目中有许多地方在Block中持有self
并不会引起循环引用(如在封装的网络请求中),但是可能持有较长的时间,建议此时使用__weak
进行弱引用处理。
__weak typeof(self)weakSelf = self;
[[NetworkRequest sharedInstance] request:@{}
successBlock:^(id _Nullable responseObject) {
weakSelf.responseObject = responseObject;
} errorBlock:^(NSError *error) {
} failureBlock:^(NSError *error) {
}];
__strong使用
如果网络异步返回Block,Block执行的过程中页面返回,由于__weak
对对象进行弱引用,则此时对象会被释放,异步运行中的对象变成nil
,引起异常。
示例:
__weak typeof(self)weakSelf = self;
[[NetworkRequest sharedInstance] request:@{} successBlock:^(id _Nullable responseObject) {
NSLog(@"weakSelf对象地址:%@",weakSelf);
for (int i = 0; i < 10000; i++) {
NSLog(@"%d",i);
}
NSLog(@"耗时的任务 结束 weakSelf对象地址:%@",weakSelf);
} errorBlock:^(NSError * _Nonnull error) {
} failureBlock:^(NSError * _Nonnull error) {
}];
打印结果
weakSelf对象地址:
...
省略
...
耗时的任务 结束 weakSelf对象地址:(null)
此时需要在Block中使用__strong
修饰符,在Block开始执行时,检验弱引用的对象是否还存在,如果存在,使用__strong
进行强引用,此时引用计数加1,这样在Block执行的过程中,这个对象就不会被置为nil
,而在Block执行完毕后,对象的引用计数就会减1,这样就不会导致对象无法释放。
__weak typeof(self)weakSelf = self;
[[NetworkRequest sharedInstance] request:@{} successBlock:^(id _Nullable responseObject) {
if (!weakSelf) return;
__strong typeof(weakSelf)strongSelf = weakSelf;
NSLog(@"strongSelf对象地址:%@",strongSelf);
for (int i = 0; i < 10000; i++) {
// 模拟一个耗时的任务
NSLog(@"%d",i);
}
NSLog(@"耗时的任务 结束 strongSelf对象地址:%@",strongSelf);
} errorBlock:^(NSError * _Nonnull error) {
} failureBlock:^(NSError * _Nonnull error) {
}];
运行结果:
strongSelf对象地址:
...
省略
...
耗时的任务 结束 strongSelf对象地址:
区别:
直接使用self
,会在编译时强引用self
,引起循环引用;
使用__weak
后再在Block中使用__strong
,则只会在运行到Block中时才会强引用,引用计数进行加1操作,Block执行完毕后引用计数减1。
- 参考及内容引用资料
iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)
(四)Block之 __block修饰符及其存储域
iOS中Block实现原理的全面分析
__weak和__strong在Block中的使用
Block的本质与使用