一:blcok本质
Block本质上也是一个oc对象,它内部也有一个isa指针。
Block 时是装了函数调用以及函数调用环境的oc对象。
二:block 结构:
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,intflags=0){
impl.isa=&_NSConcreteStackBlock;
impl.Flags=flags;
impl.FuncPtr=fp;Desc=desc;
}
};
__blockTest_block_impl_0是Block的C++实现,是一个结构体,从命名可以看出表示blockTest中的第一个(0)Block。通常包含两个成员变量__block_impl impl,__blockTest_block_desc_0* Desc和一个构造函数。
struct__block_impl{
void *isa;
int Flags;
intReserved;
void*FuncPtr;
};
__block_impl也是一个结构体
(1)*isa:isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,本例中是_NSConcreteStackBlock类型。
(2)Flags:block 的负载信息(引用计数和类型信息),按位存储。
(3)Reserved:保留变量。
(4)*FuncPtr:一个指针,指向Block执行时调用的函数,也就是Block需要执行的代码块。在本例中是__blockTest_block_func_0函数。
三:block 捕获机制
为了保证block内部能够正常访问外部的变量,blcok 有个变量捕获机制。
(1)自动变量
针对局部变量中的自动变量,会通过值传递的方式捕获到block 中,由于是值传递捕获的变量,所以在blcok内部无法感知外部的对变量的修改,并且会在block变量中复制一份该变量。 Block 之所以会通过值传递的方式保存这种变量,也是因为这种变量是分配在栈中的,一旦变量所在的函数调用完毕,该变量就会被其他内容覆盖,但是block 有可能还需要使用该变量,所以block需要捕获,并保存一份副本。
(2)静态变量
针对局部变量中的 static变量,由于static的生命周期是整个文件,所以并不存在自动变量的情况,不需要复制副本,所以通过指针传递的方式捕获该变量到block内部。由于是指针传递方式捕获,所以外部对该变量的修改,block内部可感知,并且能正常使用该变量。
(3)针对全局变量。
Block 不会捕捉全局变量,而是通过内存地址直接使用该变量。 因为全局变量的生命周期更长,不会出现被覆盖的情况。
![](https://img.haomeiwen.com/i2809976/74b0dd7b3c0da618.png)
(4)对象类型的捕获
<1>当block内部访问了对象类型的auto 变量时
如果block 是在栈上,将不会对auto变量产生强引用
<2>如果block被拷贝到堆上
会调用block内部的copy函数。Copy函数内部会调用_Block_object_assign函数。
_Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsafe_unretain)作出对应的操作,形成强引用或者弱引用。
<3>如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用 _Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量
四:block 变量的类型
Block变量有3种类型,可以通过调用class 方法或者isa指针查看具体类型,最终都是继承自 NSBlock类型。
![](https://img.haomeiwen.com/i2809976/96d7d8c0777ef157.png)
(1)__NSGlobalBlock__ (_NSconcreateGobalBlock)
当没有访问自动变量的blcok 都属于这种类型。 这种类型的block变量会存放在内存中的数据区域。
对该类型调用copy 方法,不会产生任何作用。
(2)__NSStackBlock__. (_NSConcreteStackBlcok)
访问了局部变量的block属于这种类型,这种类型的blcok变量会存放在内存中的栈中。对该类型调用copy函数后,会变成__NSMallocBlock__类型的block。
(3)__NSMallocBlock__ (_NSConcreteMallocBlock)
当__NSStackBlock__类型的blcok调用了copy函数后会成为这种类型的block。这种类型block存放在内存的堆中。
对该类型的block调用copy函数后,会使blcok对象应用计数加1.
五:ARC环境下 block的copy
在ARC 环境下,编译器会根据情况自动将栈上的blcok复制到堆上,比如以下情况
(1)block作为函数返回值时
(2)将block赋值给__strong 指针时
(3)block 作为cocoa API中方法名中含有usingBlock的方法参数时
(4)block作为GCD API的方法参数时
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
六:block 修饰符
(1)_block可以解决block内部无法修改auto变量值的问题
(2)__block不能修饰全局变量,静态变量(static)
(3)编译器会将__blcok变量包装成一个对象。
<1>当block在栈上时,并不会对__block变量产生强引用
<2>当blcok被copy到堆时,
会调用block内部的copy函数
Copy函数内部会调用_Block_object_assign 函数
_Block_object_assign函数会对__block变量形成强引用
当block从堆中移除时
会调用block内部的dispose函数
Dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose 函数会自动释放引用的__block 变量
六:__block的__forwarding 指针
(1)当block在栈上时,__forwarding指针指向自身。
(2)当block复制到堆上之后,栈中的block的__forwarding指针会指向堆上的__block变量,被复制到堆上的block的__forwarding指针仍旧指向自身。
![](https://img.haomeiwen.com/i2809976/e62052ebbd3ab379.png)
七:blcok的循环引用问题
![](https://img.haomeiwen.com/i2809976/f4f34d7d22f708da.png)
![](https://img.haomeiwen.com/i2809976/fea408074f4fb38e.png)
网友评论