前言
Blocks的原理,每当自己对知识体系有一定提升之后,再回过头来看一下曾经读过的书籍,会发现对它的理解逐步加深。借着读书笔记活动,立个小目标,把Block彻底搞明白,重读《Objective-C高级编程 iOS与OS X多线程和内存管理》
第二章节block原理部分,一方面给自己做个笔记,另一方面加深一下印象。
目录
-
Block的实质
-
Block捕获自动变量的值
-
__block的实质
-
Block存储域
-
__block变量存储域
-
截获对象
-
__block变量和对象
-
Block循环引用
1.block实质
block代码:
void (^blk)(void) = ^ { printf("Block"); }; blk();复制代码
执行xcrun -sdk iphonesimulator clang -rewrite-objc 源代码文件名
就能将含有Block的代码转换为C++的源代码。我是按照书上的示例,同样转换的main.m文件,转换完之后这里就会多出一个main.cpp
的文件,打开很恐怖,六万多行...
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Block"); }static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, char * argv[]) { void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0;}复制代码
这就是我们一直在使用的block,因为都是struct结构看上去有点抽象,不过理解起来并不难。
首先先从__main_block_func_0
函数开始,因为我们想要执行的回调看源码都是写在这个函数里面的,block使用的匿名函数(也就是我们定义的block)实际上被作为简单的C语言函数(block__main_block_func_0
)来处理,该函数的参数__cself相当于OC实例方法中指向对象自身的变量self,即__self为指向Block值的变量。__self与OC里面的self相同也是一个结构体指针,是__main_block_impl_0
结构体的指针,这个结构体声明如下:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};复制代码
第一个变量是impl
,也是一个结构体,声明如下:
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};复制代码
先看FuncPrt
,这个就是block
括号中函数的函数指针,调用它就能执行block括号中的函数,实际上在调用block的时候就是调用的这个函数指针,执行它指向的具体函数实现。 第二个成员变量是Desc
指针,以下为其__main_block_desc_0
结构体声明:
static struct __main_block_desc_0 { size_t reserved; size_t Block_size;}复制代码
其结构为今后版本升级所需的区域和Block的大小。 实际上__main_block_impl_0
结构体展开最后就是这样:
struct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0* Desc;};复制代码
还定义了一个初始化这个结构体的构造函数:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }复制代码
这就是整个__main_block_impl_0
结构体所包含的,既然定义了这个结构体的初始化函数,那在详细看一下它的初始化过程,实际上该结构体会像下面这样初始化:
isa = &_NSConcreteStackBlock;Flags = 0;Reserved = 0;FuncPtr = __main_block_func_0;Desc = &__main_block_desc_0_DATA;复制代码
__main_block_func_0
这不就是上面说到的那个指向函数实现的那个函数指针,也就是说只需要调用到结构体里面的FuncPtr
就能调用到我们的具体实现了。那这个构造函数在哪里初始化的,看上面的源码是在我们定义block的时候:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));复制代码
简化为:
struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);struct __main_block_impl_0 *blk = &tmp;复制代码
该源代码将__mian_block_impl_0
结构体类型的自动变量,即栈上生成的__mian_block_impl_0
结构体实例的指针,赋值给__mian_block_impl_0
结构体指针类型的变量blk。听起来有点绕,实际上就是我们最开始定义的blk
为__main_block_impl_0
结构体指针指向了__main_block_impl_0
结构体的实例。
接下来看看__main_block_impl_0
结构体实例的构造参数:
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);复制代码
第一个参数为由Block语法转换的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0
结构体实例指针,以下为__main_block_desc_0
结构体实例的初始化部分代码:
static struct __main_block_desc_0 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};复制代码
即__main_block_impl_0
结构体实例的大小。
接下来看看栈上的__main_block_impl_0
结构体实例(即Block
)是如何根据这些参数进行初始化的。也就是blk()
的具体实现:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);复制代码
简化以下:
(*blk->impl.FuncPtr)(blk);复制代码
FuncPtr
正是我们初始化__main_block_desc_0
结构体实例时候传进去的函数指针,这里使用这个函数指针调用了这个函数,正如我们刚才所说的,有block语法转换的__main_block_func_0
函数的指针被赋值成员变量FuncPtr
中。blk
也是作为参数进行传递的,也就是最开始讲到的__cself
。到此block
的初始化和调用过程就结束了。
2.Block捕获自动变量的值
基于上面的例子,额外增加个局部变量val:
int val = 0; void (^blk)(void) = ^{ NSLog(@"%d",val); }; val = 10; blk(); 复制代码
转换为C++代码如下:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int val = __cself->val; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val); }static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, char * argv[]) { int val = 0; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val)); val = 10; ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);}复制代码
这与上一节转换的代码稍有差异,自动变量被作为了成员变量追加到了__main_block_impl_0
结构体中。在__main_block_impl_0
结构体中声明的成员变量类型与自动变量类型完全相同(block语法表达式中,没有使用的自动变量不会被追加,也就是如果变量没有在block内被使用,是不会被捕获到的
)。
另外__main_block_impl_0
结构体的构造函数与上一篇也有差异:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }复制代码
在初始化__main_block_impl_0
结构体实例时,自动变量val
被以参数的形式传递到了结构体里面,就是在我们定义block
的时候,捕获的自动变量会被用来初始化这个结构体:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));复制代码
实际上带有这种自动变量的block会像下面这样初始化:
impl.isa = &_NSConcreteStackBlock;impl.Flags = 0;impl.FuncPtr = __main_block_func_0;Desc = &__main_block_desc_0_DATA;val = 0;复制代码
由此可以看到,在__main_block_impl_0
结构体被初始化的时候,变量val
的值被捕获到了并且赋值给了__main_block_impl_0
结构体里面的_val
成员变量,其实是值的捕获,并非内存地址,所以我们在外部无法修改。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int val = __cself->val; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val); }复制代码
__cself->val
,__cself
上一篇已经讲过了它指向的就是这个block对象
,__cself->val
就是访问的__main_block_impl_0
的成员变量_val,而自动变量的值又赋给了_val
,所以我们在外部改变自动变量的值在block内部
是不会生效的。
3.__block的实质
我们如果想要修改block
截获的自动变量的值,静态全局变量,全局变量和静态变量,block内是不会捕获到他们的值
的,所以这类变量在block内部,是可以进行改写值的。那么他们具体在代码层面上是怎么做的还是通过上面的命令看一下源码:
int global_var = 1;static int static_global_var = 2;int main(int argc, char * argv[]) { static int static_var = 3; void (^blk)(void) = ^{ global_var *= 1; static_global_var *= 2; static_var *= 3; }; blk(); return 0;}复制代码
经过转换后的源码:
int global_var = 1;static int static_global_var = 2;struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_var; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_var, int flags=0) : static_var(_static_var) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_var = __cself->static_var; // bound by copy global_var *= 1; static_global_var *= 2; (*static_var) *= 3; }static struct __main_block_desc_0 { size_t reserved; size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, char * argv[]) { static int static_var = 3; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_var)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0;}复制代码
对静态全局变量static_global_var
和全局变量global_var
的访问与转换前完全相同, 那么静态局部变量是如何转换的呢:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_var = __cself->static_var; // bound by copy global_var *= 1; static_global_var *= 2; (*static_var) *= 3; }复制代码
通过int *static_var = __cself->static_var
能够看出,实际上是将静态局部变量static_var
的指针传递给了__main_block_impl_0
结构体,这也是超出变量作用域去使用变量的一种方法
,那就是通过指针去访问。那为什么在局部变量不这么使用呢,这个后面再说,我们现在只需要知道static
修饰的局部变量是可以在block内部进行值的改变的。回归主题,那么自动变量我们是怎么去修改它的值的,就是通过__block
进行修饰,看下代码:
int main(int argc, char * argv[]) { __block int var = 10; void (^blk)(void) = ^{ var = 1; }; blk(); return 0;}复制代码
转换后如下:
struct __Block_byref_var_0 { void *__isa;__Block_byref_var_0 *__forwarding; int __flags; int __size; int var;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_var_0 *var; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_var_0 *var = __cself->var; // bound by ref (var->__forwarding->var) = 1; }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}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, char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10}; void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344)); ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); return 0;}复制代码
不难发现多了一个__Block_byref_var_0
结构体实例,它也正是__block
的实现。该结构体中的成员变量var
就相当于block外面的自动变量的成员变量。然后我们再看一下block是怎么给这个成员变量进行赋值操作的:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_var_0 *var = __cself->var; // bound by ref (var->__forwarding->var) = 1; }复制代码
刚刚在block中给静态变量赋值的时候,使用了指向该静态变量的指针,但是用__block
修饰的时候,实际上能够看到__Block_byref_var_0
结构体中也就是__block有一个成员变量__Block_byref_var_0 *__forwarding
,是一个指向该实例自身的指针,通过成员变量__forwarding
就能访问到它自身的var
,那么究竟为什么要通过这个指向自身的__forwarding
来访问成员变量var
下一节会说,我们先知道它就是使用这种方式来访问这个自动变量的。实际上我们为什么能访问到这个成员变量var
,是因为在给自动变量定义为__block类型的时候,就会初始化一个__Block_byref_var_0
类型的结构体,并且默认将该变量初始化为10(因为我们给var初始化的10),相当于持有了原自动变量的成员变量。然后在初始化__main_block_impl_0
结构体的时候就将这个block结构体
作为参数传递了过去,这样__cself->var
实际上就是我们刚才说的初始化的block的结构体,var->__forwarding->var
就是访问了这个block的结构体的__forwarding
成员变量,__forwarding
成员变量指向的又是自身,所以__forwarding->var
返回的就是自身的成员变量var
,这样整个流程就走通了,具体为什么要有个_forwarding
我们继续往下看。
4.Block存储域
通过上面的分析,现在出现了几个问题需要解释:
1.为什么要有个_forwarding
?(后面说)
2.BLock
作用域在栈上,超出变量作用域是不是就销毁了?
上面分析的block和__block都是结构体类型的自动变量,在栈上生成,称为“栈块”
,实际上还存在两种block,“堆块”
和“全局块”
。全局块与全局变量一样,设置在程序的.data数据区域
,堆块顾名思义,分配在堆上,类型分别如下:
- _NSConcreteGlobalBlock
- _NSConcreteStackBlock
- _NSConcreteMallocBlock
有两种情况,是默认会分配在数据区域上的:
- 1.记述全局变量的地方有block语法时。
- 2.block语法的表达式中不使用截获的自动变量的值。
除此之外的Block语法生成的Block为设置在栈上的_NSConcreteStackBlock
类对象。配置在全局变量上的Block从变量作用域外也可以通过指针安全的使用,但设置在栈上的Block,如果所属的变量作用域结束,该Block就被废弃。由于__block也配置在栈上,同样的__block变量也会被废弃。Blocks提供了将block和__block变量从栈上复制到堆上的方法来解决这个问题。这样即使block语法记述的变量作用域结束,堆上的block还可以继续存在(原文解释
)。大概意思就是有些情况下,编译器会默认对栈block生成一个copy
到堆上的操作。大多数情况下,编译器会适当的进行判断是否会将栈块拷贝到堆上,有一种情况除外:
- 向方法或函数的参数中传递Block。
就是说block作为参数传递的时候是需要我们手动执行copy的,编译器不会自动执行copy。尽管这样,还是有两种情况是不需要我们手动实现,因为他们函数内部已经实现了copy操作:
- Cocoa框架的方法切方法中含有usingBlock。
- GCD的API。
举个例子,把书上面的例子自己手动实现了一下:
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. id object = [self getBlockArray]; typedef void(^blk_t)(void); blk_t blk = (blk_t)[object objectAtIndex:0]; blk();}- (id)getBlockArray { int var = 10; return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk0:%d",var);}, ^{NSLog(@"blk1:%d",var);}, nil];}复制代码
在执行blk()
的时候程序异常崩溃了。因为getBlockArray
函数执行结束的时候,在栈上创建的block被废弃了,这个时候编译器并没有自动执行copy操作,需要我们手动实现。为什么编译器不对所有的栈块都执行copy到堆上,书上明确说明了:block从栈复制到堆上是相当消耗CPU的,将block设置在栈上也能够使用时,将block复制到堆上只是在浪费CPU资源。
所以这种情况下对block执行copy就可以了:
- (id)getBlockArray { int var = 10; return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk0:%d",var);} copy], [^{NSLog(@"blk1:%d",var);} copy], nil];}复制代码
2018-12-24 12:33:33.526163+0800 Blocks-捕获变量的值[54592:3223484] blk0:10复制代码
文中还提到了如果多次调用copy会不会有问题,答案当然是没有问题,在ARC下是不用担心多次copy引起内存问题。
还有一个_forwarding
的问题没有说,至少现在已经知道我们设置在栈上的block因为执行了copy操作到了堆上,所以我们无需关心它会超出作用域而被释放的问题了,那么_forwarding
继续往下看。
5.__block变量存储域
如果在Block中使用了__block变量,那么该Block从栈复制到堆时,所使用的__block也会被复制到堆上,并且会被Block持有。每一个使用了当前__block变量的Block被复制到堆上时都会对这个__block引用计数+1,如果配置在堆上的Block被废弃,相应的它所使用的__block引用计数会-1,直到所有的Block被释放那么此__block也会随之释放。
也就是说,除了上面说到的两种情况,我们其余的Block基本都会复制到堆上,也就是说我们使用的__block也会相应的跟着复制到堆上,像OC对象一样,拥有引用计数。那么我们再分析一下之前遗留的问题,_forwarding
是干嘛的,当__block被复制到堆上的时候,栈上面的__block结构体里面的_forwarding
成员变量就会指向堆里面的__block结构体实例,此时堆上面的__block变量的_forwarding
会指向自己本身。也就如下图这个样子:
回顾一下上面__block的实质
举过的例子,我们在用__block修饰自动变量的时候,在func函数里面修改此变量值的时候,通过(var->__forwarding->var) = 1;
这种方式去改变的,var->__forwarding
实际上访问的是堆上的__block结构体,var->__forwarding->var
就是堆里面结构体的var成员变量。这样就算是栈上面的__block被释放了,我们还可以去访问堆里面的var,这也是为什么自动变量不像static静态变量那样通过指针去访问了,因为自动变量在作用域结束之后就会被释放了,拷贝到堆上,作用域结束堆上面还会有其相应拷贝,这份拷贝只有在使用了它的Block释放之后才会释放。
6.截获对象
前面分析了__block修饰的自动变量超出作用域也能使用的原理,实际上对于对象类型,Block对其捕获之后在处理上和__block很像,那么具体使用__block对变量捕获之后当Block和__block被拷贝到堆上和他们被释放这两个过程具体做了什么之前也没有详细讲到,通过捕获对象的学习,也可以对前面做个总结和思考。直接看下书上面的示例代码:
typedef void(^blk_t)(id); blk_t blk; { id array = [[NSMutableArray alloc] init]; blk = [^(id obj){ [array addObject:obj]; NSLog(@"array count : %ld",[array count]); } copy]; } blk([[NSObject alloc] init]); blk([[NSObject alloc] init]); blk([[NSObject alloc] init]);复制代码
2018-12-25 12:25:06.678625+0800 Blocks-捕获变量的值[56349:3341197] array count : 12018-12-25 12:25:06.679199+0800 Blocks-捕获变量的值[56349:3341197] array count : 22018-12-25 12:25:06.679210+0800 Blocks-捕获变量的值[56349:3341197] array count : 3复制代码
按理来说array在超出变量作用域的时候会被废弃,但是根据打印结果来看一切正常。也就是说array在超出变量作用域后依然存在。通过转换的源码如下:
Block结构体部分:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) { id array = __cself->array; // bound by copy ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_1dc794_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count"))); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}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};复制代码
使用Block部分:
typedef void(*blk_t)(id); blk_t blk; { id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")); blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy")); } ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))); ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))); ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));复制代码
这一块我自己测试了一下暂时有点疑问。因为按照书上的示例来看,array前是有__strong修饰的,但是从我转换的源码来看并未看到__strong修饰符。是否是说如果没有使用weak修饰默认为strong?
我先默认这个id array是被__strong修饰的。文中讲到,C语言结构体不能附有__strong修饰符的变量,因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存。但是它能很好的把握Block从栈复制到堆和把Block从堆上废弃的时机,因此就算Block结构体中含有OC修饰符的变量也一样能够跟随者Block的废弃而废弃。
因为Block结构体中含有__strong修饰符的对象,所以需要对它进行管理,和之前的Block源码对比,在struct __main_block_desc_0
结构体中多了两个函数指针:
- void (copy)(struct __main_block_impl_0, struct __main_block_impl_0*);
- void (dispose)(struct __main_block_impl_0);
这其实在分析__block原理
的时候就有了,实际上他们用处是一样的,都是用来管理Block内存用的。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}复制代码
_Block_object_assign
函数相当于调用了retain函数,将对象赋值在对象类型的结构体成员变量中。
static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}复制代码
_Block_object_dispose
函数相当于调用了release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。但是从转换的源码来看,__main_block_copy_0
和__main_block_dispose_0
函数指针都没有被调用,那么它们是在什么时候触发的?
- 栈上的Block复制到堆时 --> 触发copy函数。
- 堆上的Block被废弃时 -->触发dispose函数。
Block被废弃上面说了就是没有对象强引用它就会被回收了,就会调用dispose方法。那么什么时候栈上的Block会复制到堆上呢?
- 1.调用Block的copy实例方法。
- 2.Block作为函数返回值返回时。
- 3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时。
- 4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时。
这样通过__strong修饰符修饰的自动变量,就能够在作用域外使用了。
在前面使用__block的时候实际上这两个函数就已经用到了,略微有点不同之处:
- 截获对象时 -->
BLOCK_FIELD_IS_OBJECT
- __block变量时 -->
BLOCK_FIELD_IS_BYREF
通过这两个参数用来区分是Block捕获的是对象类型还是__block变量。除此之外他们在copy和dispose时都是一样的,都是被Block持有和释放。
7.__block变量和对象
对于Block截获__block
修饰的变量还是直接截获对象的处理过程,上面都已经分析完了,包括它们关于内存的处理也都清晰了,唯独使用__block
修饰id类型的自动变量还没有说,实际上__block
说明符可以指定任何类型的自动变量,当然包括对象类型。还是按照书上面的例子看下代码:
__block id obj = [[NSObject alloc] init];复制代码
等同与
__block id __strong obj = [[NSObject alloc] init];复制代码
通过clang转换如下:
/* __block结构体部分*/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*); id obj;};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变量声明部分*/__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = { (void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")) };复制代码
这里出现了上一节讲到的_Block_object_assign
和_Block_object_dispose
函数。实际上编译器默认这个obj为__strong
类型,当Block从栈复制到堆上时,使用_Block_object_assign
函数持有Block截获的对象,当堆上的Block被废弃时,使用_Block_object_dispose
函数释放Block截获的对象。这说明使用__block修饰的__strong类型的对象,当__block变量从栈复制到堆上并且在堆上继续存在,那么该对象就会继续处于被持有状态。这与Block中使用赋值给附有__strong
修饰符的对象类型自动变量的对象相同。
那么除了__strong
,如果用__weak
修饰呢?
__weak
修饰的对象,就算是使用了__block
修饰,一样还是会被释放掉,实际上书上的源代码也是给了我们这样一个结论,只不过对象会自动置为nil
。而使用__unsafe_unretained
修饰时,注意野指针问题。
8.Block循环引用
避免循环引用,根据Block的用途可以选择使用__block变量,__weak修饰符和__unsafe_unretained修饰符来避免循环引用。
__weak和__unsafe_unretained修饰弱引用,不用考虑释放问题。
__block修饰,属于强引用,需要在Block内对修饰的对象置nil,为避免循环引用必须执行Block,但是__block变量可以控制对象的持有时间。