最近在看SDWebImage的源码。发现block这块的知识很深,源码里用的有些地方不知道什么作用。所以研究了下。
深层分析Block的实质:它是Objective-C对象。
block 实际上就是 Objective-C 语言对于闭包的实现,闭包就像是C++中的函数指针。为什么说Block其实就是Objective-C对象,因为它的结构体中含有isa指针。
通过该图,我们可以知道,一个 block 实例实际上由 6 部分构成:
isa 指针,所有对象都有该指针,用于实现对象相关的功能。
flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。
reserved,保留变量。
invoke,函数指针,指向具体的 block 实现的函数调用地址。
descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
block的分类
NSConcreteGlobalBlock全局Block
全局Block存储在全局区。在编译期间就已经决定了,如同宏一样。
没有用到外界变量,或者只用到全局变量、静态(static)变量的block就是全局block。
对于全局block,有没有指针引用都不影响。
NSConcreteStackBlock栈Block
生命周期由系统控制,函数返回即销毁
用到局部变量、成员属性\变量,且没有强指针引用的block都是栈block
我们已经知道了NSConcreteStackBlock,那么它和NSConcreteGlobalBlock有什么区别呢?难道仅仅是引用了外部变量与否的区别吗?答案是否定的.
其实NSConcreteStackBlock内部会有一个结构体__main_block_impl_0,这个结构体会保存外部变量,使其体积变大。而这就导致了NSConcreteStackBlock并不像宏一样,而是一个动态的对象。而它由于没有被持有,所以在它的内部,它也不会持有其外部引用的对象。
证明一下
看到了吧,引用计数没变,发现指针的地址&obj在block中的和在block外的不一样。验证了__main_block_impl_0,这个结构体会保存外部变量。
NSConcreteMallocBlock堆Block
NSConcreteMallocBlock其实就是一个栈block被copy时,将生成NSConcreteMallocBlock。
NSConcreteMallocBlock是会持有外部对象的
看到了吧,只要这个NSConcreteMallocBlock存在,内部对象的引用计数就会+1。
__block 关键字
没错,前文说过,block引用外部是以捕获的形式来捕捉的,而没有声明__block,则会将外部变量copy进block,若用了__block,则是复制其引用地址来实现访问。这就是为什么声明了__block,在block内部改变就会对外有影响的原因了。一个是值传递,一个是引用传递。
注意!!这里需要知道的是,在MRC环境下,如果没有用__block,会对外部对象采用copy的操作,而用了__block则不会用copy的操作。
从更底层的角度来说,在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以要谨防循环引用的问题!
ARC下的 block
大家普遍会认为ARC下不存在NSConcreteStackBlock,这是因为本身我们常常将block赋值给变量,而ARC下默认的赋值操作是strong的,到了block身上自然就成了copy,所以常常打印出来的block就是NSConcreteMallocBlock了。所以在ARC下,大部分的应用场景下,几乎可以说是全部都为NSConcreteMallocBlock或者是NSConcreteGlobalBlock。
关于block作为属性用什么修饰。其实用copy和strong都可以的,strong修饰其实也是copy。但是苹果建议我们用copy。我一我们一般写都用copy修饰block。
block 的循环引用问题。
我们知道NSConcreteMallocBlock是会持有外部变量的,而此时如果它所持有的外部变量正好又持有它,就会产生循环引用的问题。
self强引用myBlock, myBlock强引用了self,所以导致self无法释放。
你可以用__weak(ARC)或__block(MRC)来解决:
block对于以参数形式传进来的对象,不会强引用
哈哈没看到有黄色的⚠️。所以不会强引用。