void (^blk) (void) = ^{pringf("block \n");};
这个block的其实就是编译成一个函数指针void (*blk) (void) = &impl_0(func_0, &desc_0)
impl_0 func_0 desc_0都是结构体,impl_0的构造函数是以func_0,desc_0型变量为参数的。
blk(); 使用block其实就是调用函数指针,(*blk->impl.FuncPtr)(blk); (func_0以impl_0为参数)
block如何截取局部变量的值的?
在编译的时候,如果block声明前有局部变量,并在block内被使用了,在impl_0的构造函数的参数列表就有这个局部变量,并且使用局部变量的值来初始化;在impl_0的成员变量也添加了这些成员变量。
在block声明前如果有静态局部变量,
static int static_val = 3;
相当于在impl_0内增加了int *static_val成员变量,使用该变量时即是使用它的指针进行访问。
为什么非静态的局部变量没有用这个方式来进行访问呢,如果这样就不存在block截获局部变量的值的情况了。因为如果局部变量被截获后(在block声明处被截获),如果该局部变量作用域结束,这个局部变量就被废弃了,block中就不能再对它访问了。
__block实现
__block int val = 0;
被编译成:(括号里面是赋的值)
struct __Block_byref_val_0{
void *__isa;(0)
__Block_byref_val_0 * __forwarding;(&val)
int __flags;(0)
int __size;(__Block_byref_val_0)
int val;(10)
}
block内对val赋值:
^{val = 1;}
在func_0内被编译成,在impl_0中同样会添加成员变量
{
__Block_byref_val_0 *val = __cself->val;
val->__forwarding->val = 1;
}
Block存储域
一般block设置在栈上,_NSConcreteStackBlock。isa=_NSConcreteStackBlock
有两种情况在global数据区 即isa=_NSConcreteGlobalBlock:
1、block声明为全局变量
2、block中不截获局部变量的值(执行时不依赖局部变量的值,整个程序只需一个实例)
设置在栈上有个问题,就是声明block时的变量作用域结束了,栈也就没了,block和__block变量也都没了,所以arc时,大多数情况下编译器会自动生成将block从栈上复制到堆上的代码来解决这个问题。
比如下面这个将block作为函数返回值:
blk_t func(int rate)
{
return ^(int count){return rate * count;};
}
blk_t func(int rate)
{
blk_t tmp = &_func_block_impl_0(func_0,desc_0,rate);
tmp = _Block_copy(tmp);从栈复制到堆
return objc_autoreleaseReturnValue(tmp); 注册到autoreleasePool并返回
}
有些情况编译器不能判断:
向函数参数传递block(例外:函数里面已经复制了参数的就不用在函数前手动复制了,还有框架的某个方法名中有usingBlock,GCD的API里)
对block调用copy方法时,原来在栈上的block复制到堆上;原来在数据区的,什么也不做;原来在堆的,引用计数加1
那么block中使用的__block变量在block从栈复制到堆上又发生了什么?
同样是从栈复制到堆,并依然被该block所持有。
__block变量在从栈复制到堆上的时候,将__forwarding指向堆上的__block变量,所以不论是访问栈上的(在block外使用__block变量)还是访问堆上的(block内使用__block变量),都能通过val.__forwarding->val来访问。(访问的都是堆上的??)
block持有截获的局部变量(如果是weak类型呢??)
typedef void(^blk_t)(id obj);
blk_tblk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id b){
[array addObject:obj];
NSLog(@"array count %ld",[array count]);
}copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
block在编译后成员变量添加了id __strong array;
由于[block copy]的调用,多了__main_block_copy_0和__main_block_dispose_0方法,在__main_block_copy方法中调用__Block_object_assign将对象赋值给array成员变量并持有该对象,同理,__main_block_dispose_0方法中调用_Block_object_dispose释放赋值给array的对象。
所以在大括号结束时,即便array已经超过变量作用域,堆上的block依然持有该对象,不能被废弃。
block从栈赋值到堆有四种情况:
1、[block copy]
2、return block; block作为函数返回值
3、id strong blk = block; / self.blk = block;
4、xxxusingBlock(blk_t blk) / GCD的API用block做参数的
变成weak之后 发现block结构体中的成员变量也变成weak了
array2在出变量作用域之后设为nil,所以三次输出都是0
可用声明为weak来避免循环引用。
看一下block截获基础类型变量和对象类型变量的区别
__block int val = 10;
该变量转换为结构体__Block_byref_val_0
同时转换后的文件头部多了___main_block_copy_0和___main_block_dispose_0这两个函数定义,
这两个函数内部调用了__Block_object_assign和_Block_object_dispose方法,
__Block_object_assign方法中让Block持有__block变量
这两个方法有个参数block_field_is_byref(绿色的)代表是assign了__block变量,如果是id类型,但是没有__block修饰符,则这里的值是BLOCK_FIELD_IS_OBJECT。
具体的__Block_object_assign实现是这样的:
这两个函数在main中并没有被调用,那么是何时进行了调用呢?
当block从栈复制到堆上的时候,调用__main_block_copy_0,__block变量也从栈复制到堆上时,并被Block所持有。
__block id obj = [[NSObject alloc] init];的情况
__Block_object_assign函数将__block变量赋值给block成员变量,strong类型变量持有对象,从而block持有该对象
只要__block变量在堆上存在,对象就会一直被持有。
__block id __strong obj = [[NSObject alloc] init]; 和不带__strong的情况一样
总结一下就是:
ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量如int而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改。而MRC下,则都是拷贝一份指针。