导语:今天我们探讨一下block,到底是如何工作的,有些东西可能比较不好理解,但是我认为,清楚几个重点就行了,首先block怎么使用(大家都用过,就不多说),变量如何捕获的,block如何修改外部变量,如何不形成循环引用基本就可以了。有兴趣当然可以深入了解。另外本人知识储备有限,如果有错漏之处,请一定要指正,非常感谢。
block定义
-
block本质上就是OC对象
-
block是封装了函数实现以及函数调用环境的OC对象,本身也是带有isa指针的
block底层结构图如下:
Block_layout | Block_descriptor | |
---|---|---|
(void*) isa | (unsighed long int) reserved | |
(int) flags | (unsighed long int) size | |
(int) reserved | (void *(void *)) copy | |
(void *(void *, ...)) invoke | (void *(void *)) dispose | |
(strut Block_descriptor *) descriptor | descriptor -> Block_descriptor | |
variables |
block变量捕获
- block内部如果要使用局部变量,首先需要捕获局部变量
block 直接引用 | block变量捕获 |
---|---|
全局变量 | auto修饰的局部变量(值捕获,内部创建一个新的局部变量) |
函数的参数 (包括self,_cmd这两个隐藏参数) | |
static 修饰的局部变量(捕获指针) |
不加修饰符的局部变量,默认就是auto修饰。
方法中的block如果使用了self,或者函数的其他参数,block是会捕获这些变量的。
如果是auto修饰的局部变量,block会直接捕获局部变量的值,在block内部会直接生成一个新的同名变量储存这个局部变量。
如果是 static 修饰的局部变量,block会捕获此布局变量的指针
如果是全局变量,无论是不是static修饰的,都不会捕获,直接引用
- 为什么不直接捕获auto修饰的局部变量的指针呢?
是因为auto修饰的局部变量出了作用域就会销毁,有可能在此局部变量销毁之后调用block,如果直接捕获指针,就会产生野指针错误。而static修饰的局部变量属于静态变量,出了作用域只是不能直接访问,不会销毁,可以通过指针访问。
block类型
block类型 | 存储位置 |
---|---|
__NSGlobalBlock__ | 存在数据段区 |
__NSStackBlock__ | 存在栈区(容易被销毁,要保住copy到堆区) |
__NSMallocBlock__ | 存在堆区 |
__NSGlobalBlock__
: 没有捕获auto修饰的局部变量的block就是NSGlobalBlock类型(包括捕获了static修饰的局部变量的block也属于NSGlobalBlock)
__NSStackBlock__
:捕获auto修饰的局部变量的block就是NSStackBlock类型
__NSMallocBlock__
:__NSStackBlock__
的block调用copy就会生成__NSMallocBlock__
类型的block
block的copy
- 在ARC自动管理内存模式中,只要有强指针指向block,会自动调用copy操作
- block作为函数返回值的时候也会调换用copy操作
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
//MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
//ARC下block属性的建议写法
@property (strong, nonatomic) void (^block1)(void);
@property (copy, nonatomic) void (^block2)(void);
对象类型的auto变量
-
如果block是在栈上,无论某个对象的auto变量是否是强指针,block也不会产生强引用(因为block随时会被销毁).
-
如果block被copy到了堆上
- 会调用block内部的copy函数
- copy函数内部会调用
_Block_object_assign
函数_Block_object_assign
函数会根据auto变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用
总之,block内部会根据auto变量的修饰符做出反应,形成强引用或者弱引用
- 被copy的block内部会新增copy,dispose两个函数
copy 栈上的函数copy到堆上
dispose 堆上的block被废弃时调用
__block
修饰符
__block
修饰变量
block内部默认无法修改外面的局部变量的。
只有被
__block
修饰的局部变量就可以在block内部修改。
__block
修饰符只能用来修饰auto修饰的变量。
-
__block
的改值原理是:经过__block
修饰的auto变量,编译器会把这个变量包装成一个对象(之前的auto变量会成为这个对象中的一个成员变量),通过地址指针修改变量值。 -
内存管理: 原则上跟对象类型的auto的内存管理一样,如果block在栈上,不会对
__block
修饰的变量强引用,如果block被copy到堆上,会对__block
修饰的变量强引用。 -
当block在堆上被移除时,会调用block内部的
dispose->_Block_object_dispose->自动释放__block修饰的变量
总结:通过__block
修饰的变量可以看做一个对象,因此可以通过指针来修改变量值。
__block
修饰对象
-
如果
__block
变量在栈上,不会对指向的对象产生强引用。 -
当
__block
变量被copy到堆时
会调用
__block
变量内部的copy函数,copy函数内部会调用_Block_object_assign
函数。
_Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)。
- 如果
__block
变量从堆上移除。
会调用
__block
变量内部的dispose函数。dispose函数内部会调用
_Block_object_dispose
函数。
_Block_object_dispose
函数会自动释放指向的对象(release)。
block循环引用
- 开发中,我们经常遇到block循环引用的问题。
最经典的循环引用就是,block作为一个属性被对象强引用,而block内部又使用了self,block又强引用了对象。
通常使用
__weak
(__weak typeof(self) weakSelf = self
),__unsafe_unretained
来解决这个问题。其实我们还可以使用
__block
来解决循环引用的问题,不过一定要调用block。(知道__block
也有这个作用就好,开发中应该使用__weak
)。循环引用这个问题,网上非常多资料,在此就不多说了。
- 有些资料是引用了网上的,如果有不明白或者有误可以直接指出,我们一起探讨,感谢。