__block变量生成的会跟着Block从栈上复制到堆上,例如有两个Block使用了这个__block变量,一开始和Block一
样是初始化在栈上的,但是当有个Block复制到堆上的时候时,__block也跟着被复制,当剩下的Block也一并被复制到
堆上的时候,上一节有讲,Block和__block其实都是对象,因此__block的引用计数就会增加,当配置在堆上的Block
废弃的时候,__block也一并被废弃,完全可以用OC引用计数管理思考模式来思考他的生与死,那么我们现在来讲个
例子__block修饰的_forwarding无论在堆上还是在栈上都能访问变量的原理。
代码就这么简单,看看转换成的源代码
__block int val = 0;
void (^blk)(void) = [^{++val;} copy];
++val;
blk();
NSLog(@"%d%@,%@",val,^{++val;},blk);
转换后的源代码太长了,我截取一部分观察
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
++(val->__forwarding->val);}
第一:
__block_byref_val_0就是__block转换的内部结构体,内部有__forwrading指针(Block在栈上就指向__Block_byref_val_0结构体也就是自身,当Block在堆上的时候就指向堆上的__block_byref_val_0结构体指针)
第二:
为什么isa指针还是指向stack?
impl.isa = &_NSConcreteStackBlock;
看看咱们打印的结构就知道了,复制前是在栈上的,复制后就到堆上了
NSLog(@"%d%@,%@",val,^{++val;},blk);
测试[1368:35038] 2<__NSStackBlock__: 0x7fff5fbff708>,<__NSMallocBlock__: 0x1002026d0>
第三:
虽有访问__block对象的地方,无论是复制后的还是复制前在栈上的,都是通过
++(val->__forwarding->val);
看一张图
可以看出,__block变量复制前后,指针的变化情况,那么当你无论在哪里访问这个__block变量,最终都是指向同一个,如果已经复制,那么就是堆上的那个结构体,没有复制就是栈上指向自己的那个,可以用OC里面的重载来看这个问题,当子类实现了方法之后,那么实例化的对象访问该方法都会直接从派生在最外层的类中来获取需要的内容,这个也是如此。
这里有两个很奇怪的函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
我们现在展示的是结果局部assign类型的变量,但是当我们结构id obj = [NSObject New];这种对象strong类型的时候,这两个函数就变了,注意细看最后一个单词
捕获的是对象类型的值 BLOCK_FIELD_IS_OBJECT
不会的是__block类型 BLOCK_FIELD_IS _BYREF
可以把这两个函数理解为retain和release,和对象一样管理内存
因此,当Block截获了strong类型的对象时,该类型根本Block复制到堆上,被Block所持有,不会因为超出作用域被销毁,当Block存在,就会持有该变量,就能正常访问了
讲到这里,终于可以理解为什么Block里面不能用持有Block这个对象的任何属性包括自身会造成循环引用了吧。。。
如果在控制器里面 blk = ^{NSLog(@"%@",self)}; 这里的self就是控制器,那么很明显就会造成循环引用了
控制器持有Block,Block根据上面提到的,对于strong类型会retain一次,那么Block也持有了控制器,这样就谁都无
法释放了,终于能解释这个问题了。。。。。。
如果结果的是控制器里面的某个属性例如obj,其实访问这个的时候被转换成sefl->obj,原理也就是还是访问了self,一样的循环引用
解决问题大家应该很常见了,把self弱引用穿进去就可以了,出了作用域自然就废弃了
第一种
__weak typeof(self)weakSelf =self;
第二种
__block id temp = self;
blk = ^{
NSLog(@"%@",temp);
temp = nil;
};
通过对象持有Block,Block持有__Block类进行操作,当执行完Block的时候把__block对象释放即可。
通过该方法的优点就是能控制对象持有时间
缺点就是必须执行Block,不然必然释放不掉
在很早之前,ARC无效的情况下,__block说明符是用来避免Block重的循环引用的,这是由于当Block从栈复制到堆时,若Block使用的变量有__block说明的类型,不会被retain
看了四天的资料,终于写的差不多了,我也就理解这么一丢丢,源码看多了整个人都不好了,还是知道了原理,没白
看,以后忘了再来翻翻就好了
需要看前面分析的直接戳下面几个
Block源码分析系列(一)————2分钟明白Block究竟是什么?
Block源码分析系列(二)————局部变量的截获以及__block的作用和理解
Block源码分析系列(三)————隐藏的三种Block本体以及为什么要使用copy修饰符