block本质:
每个OC对象内部都有isa指针,block也不例外;block其实就是封装函数调用,以及函数调用环境的OC对象。
block底层结构:
block底层结构block的类型:
内存几大区域常见的有以下三种block:
NSMallocBlock :存放在堆区的 Block
NSStackBlock : 存放在栈区的 Block
NSGlobalBlock : 存放在全局区的 Block
当block 没有截获外部变量、截获全局变量的都是属于全局区的 Block,即 GlobalBlock;其余的都是栈区的 Block,即 StackBlock;
对于全局区的 Block,是不存在作用域的问题,但是栈区 Block 不同,在作用域结束后就会 pop 出栈,__block 变量也是在栈区的,同理作用域结束也会 pop 出栈。
为了解决作用域的问题,block 提供了 copy 函数,将 block 从栈复制到堆上,在 MRC 环境下需要我们自己调用 block_copy 函数,这里就是为什么 MRC 下,我们为什么需要用 copy 来修饰 Block 的原因。
MRC情况下:
MRC的情况下ARC情况下:
编译器会根据情况自动添加copy,将栈上的block拷贝到堆上,比如以下情况:
1、block作为函数返回值
2、block赋值给__strong指针
3、block作为Cocoa API中的方法名含有usingBlock的方法参数时
4、block作为GCD API参数时
每一种类型的block调用copy后的结果如下:
使用copy结果示意图block捕获机制:
block捕获机制 捕获auto、static局部变量因为auto的局部变量在离开作用域会销毁,所以block内部使用值传递的方式捕获到,因此在block内部不能更改auto变量的值,像int因为值是直接存储在变量内存里面,所以不能更改;而static因为是指针传递所有能更改。
更改mArr内容 更改mArr指针地址因为NSMutableArray 变量里面其实存的是指针地址,所以直接操作指针里面的内容是可以的,如果直接更改变量存储的指针地址就不行。
如果是全局变量不会捕获,不管在哪个函数都能直接访问;局部变量因为作用域的问题,可能存在跨函数访问所以会捕获。
如果block中引用实例变量(比如说name)不管是通过self.name还是_name,block都会捕获self对象.因为方法的调用默认传递两个参数(self,sel)所以self为局部变量,block引用局部变量都会捕获。
既然block对局部auto变量是值传递,那么如果想要在block内部修改auto变量怎么修改?
如果block外部变量是auto变量可以使用__block;如果是static静态变量 或者全局变量,则不需要修饰也能更改。(参考捕获机制)
原理:在ARC环境下,编译器会尽可能给我们自动添加 copy 操作。使用copy从栈复制到堆上,__block 修饰的变量也会从栈复制到堆上;为了结构体 __block 变量无论在栈上还是在堆上,都可以正确的访问变量,我们需要 forwarding 指针;forwarding指针的作用:保证当我们将 Block 从栈拷贝到堆中,修改的变量都是同一份。
forwarding原理:在 Block 从栈复制到堆上的时候,原本栈上结构体的 forwarding 指针,会改变指向,直接指向堆上的结构体。这样子就可以保证之后我们都是访问同一个结构体中的变量,这里就是为什么 __block 修饰的变量,在 Block 内部中可以修改的原因了。
forwarding指针__block底层实现:
当__block变量在栈上时:
并不会对指向的对象产生强引用。
当__block变量被copy到堆上时:
copy函数内会调用_Block_object_assign函数;_Block_object_assign函数会根据auto变量(对象类型)的修饰符(__strong,__weak, __unsafe_unretained)做出相应的操作形成强、弱引用;
当__block变量被从堆上移除时:
会调用Block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量,类似于release。
block引起循环引用场景:
当 A 持有 B,B 又持有 A,这个时候就会出现循环引用。Block 对于外部变量都会追加到结构体中,所以在实现 Block 时候需要注意这个问题。
解决方案:
ARC 环境一般我们用 __weak 来打破,MRC 环境的话,我们可以使用 __block 来打破循环引用。
网友评论