Blocks能够用一句话来归纳:带有自动变量的匿名函数。关于Blocks的语法和用法,本文不在过分赘述。而是汇集于Blocks的本质究竟是什么?他是怎么实现的?ios
Block其实是C语言的扩充,也就是说,Block语法源代码是会被编译为普通的C语言源代码的。经过clang能够将其转换为咱们可读代码,例以下面代码:程序员
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
printf("hello world");
};
blk();
return 0;
}
复制代码
经过clang转换后的代码:编程
//block实现结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block代码块中的实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
//block描述结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
//block实现
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//block调用
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
简单的几行代码转换后居然增长了这么多,可是仔细看,其实并不难理解。能够分为两部分:实现block、调用block。markdown
实现block
转换后是经过下面代码实现block的:多线程
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
复制代码
它调用了__main_block_impl_0结构体来实现,而该结构体又是分别包含__block_impl结构体和__main_block_desc_0结构体2个成员变量框架
// impl结构体
struct __block_impl {
void *isa; // 存储位置,_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
int Flags; // 按位表示一些 block 的附加信息
int Reserved; // 保留变量
void *FuncPtr; // 函数指针,指向 Block 要执行的函数,即__main_block_func_0
};
// Desc结构体
static struct __main_block_desc_0 {
size_t reserved; // 结构体信息保留字段
size_t Block_size; // 结构体大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
复制代码
再来看__main_block_impl_0结构体的构造函数:函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
复制代码
其中第一个参数须要传入函数指针,第二个参数是做为静态全局变量初始化的__main_block_desc_0结构体实例指针,第三个参数flags有默认值0。重点看第一个参数,他其实就是block语法生成的block函数:oop
^{ printf("hello world"); };
复制代码
通过转换后__main_block_func_0函数指针:spa
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
复制代码
再来重点看__main_block_impl_0构造函数的一行代码:线程
impl.isa = &_NSConcreteStackBlock;
复制代码
将_NSConcreteStackBlock地址赋值给isa。咱们再回顾下objc_object的实现,其也包含isa指针。__main_block_impl_0结构体至关于基于objc_object结构体的oc类对象的几多题。其成员变量isa经过_NSConcreteStackBlock初始化。即_NSConcreteStackBlock至关于class_t结构体实例。在将block做为对象处理时,其类信息放置于_NSConcreteStackBlock中。
调用block
调用block就相对简单多了。将第一步生成的block做为参数传入FucPtr(也即_main_block_func_0函数),就能访问block实现位置的上下文。
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
复制代码
总结:block其实是经过block_impl结构体实现的,而该结构体的首地址是isa,所以在objc中,block实际上就算是对象。
经过上面咱们已经理解block匿名函数的本质了,那么带有自动变量又是指什么呢?先看下面这段代码:
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
printf("val = %d", val);
[array addObject:@"obj"];
printf("%lu", (unsigned long)[array count]);
};
blk();
return 0;
}
复制代码
经过clang转换后的代码,咱们主要来看其中的不一样之处,首先来看__main_block_impl_0结构体:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;//编译时就自动生成了相应的变量
__strong id array; //注意这里
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, __strong id _array, int flags=0) : val(_val) , array(_array) {
impl.isa = &_NSConcreteStackBlock;//block的isa默认是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
首先来看__main_block_impl_0结构体内申明的成员变量类型与自动截取的变量类型彻底相同。block表达式未使用的自动变量不会追加到结构中,例如dmy。另外,该结构体的构造函数中,加入了自动变量的初始化。 再来看看匿名函数的实现:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy 值拷贝,即 val = 10,此时的a与传入的__cself的val并非同一个
printf("val = %d", val);
__strong id array = __cself->array; // bound by copy
[array addObject:@"obj"];
printf("%lu", (unsigned long)[array count]);
}
复制代码
同时,转换后的代码里还多了下面这些代码,他们是用来干什么的?咱们暂且不表,将在__block说明符章节中作详细说明:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->array,
(void*)src->array,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->array,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);//注意这里
void (*dispose)(struct __main_block_impl_0*);//注意这里
}
__main_block_desc_0_DATA =
{
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
复制代码
经过以上代码咱们能够知道:
前面咱们说到block也是oc对象,按照isa对应的class_t信息来分类,block能够分为如下三种:
经过命名咱们能够猜想出,他们对应的block对象分别存储在全局、栈、堆上
_NSConcreteGlobalBlock
block是_NSConcreteGlobalBlock的状况有如下两种:
void (^glo_blk)(void) = ^{
NSLog(@"global");
};
int main(int argc, const char * argv[]) {
glo_blk();
NSLog(@"%@",[glo_blk class]);
}
复制代码
int glo_a = 1;
static int sglo_b =2;
int main(int argc, const char * argv[]) {
void (^glo_blk1)(void) = ^{//没有使用任何外部变量
NSLog(@"glo_blk1");
};
glo_blk1();
NSLog(@"glo_blk1 : %@",[glo_blk1 class]);
static int c = 3;
void(^glo_blk2)(void) = ^() {//只用到了静态变量、全局变量、静态全局变量
NSLog(@"glo_a = %d,sglo_b = %d,c = %d",glo_a,sglo_b,c);
};
glo_blk2();
NSLog(@"glo_blk2 : %@",[glo_blk2 class]);
}
复制代码
_NSConcreteStackBlock和_NSConcreteMallocBlock
ARC有效时,如下几种状况,编译器会进行判断,自动将栈上的Block复制到堆上:
例以下面这段代码:
int num = 10;
//输出_NSConcreteStackBlock
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
void (^block)(void) = ^{
NSLog(@"%d",num);
};
//输出_NSConcreteMallocBlock
NSLog(@"%@",[block class]);
复制代码
除此以外,都推荐使用block的copy实例方法把block复制到堆上。例以下面这个例子:
id getBlockArray()
{
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"blk0:%d", val);},
^{NSLog(@"blk1:%d", val);}, nil];
}
int main(int argc, char * argv[]) {
id obj = getBlockArray();
void (^blk)(void) = [obj objectAtIndex:1];
blk();
return 0;
}
复制代码
运行程序崩溃,由于NSArray内的block类型为_NSConcreteStackBlock,getBlockArray函数执行完成后,就被自动释放废弃了,再执行[obj objectAtIndex:1]时,就发生异常。
为了解决上述问题,经过手动copy复制到堆上便可:
id getBlockArray()
{
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"blk0:%d", val) ;} copy],
[^{NSLog(@"blk1:%d", val);} copy], nil];
}
复制代码
再回到截获自动变量值的例子,假如咱们在block中试图改变自动变量val。
int main(int argc, const char * argv[]) {
NSMutableArray *array = nil;
int val = 10
void (^blk)(void) = ^{
val = 1;
array = [NSMutableArray array];
printf("val = %d", val);
};
blk();
return 0;
}
复制代码
结果编译器报错。由于不能改写被截获的自动变量的值,我的猜想,若是在block内修改自动变量的值是可行的,那么修改的应该是结构体内的临时变量,与自动变量互不影响。这很容易引发开发者犯错,为了不这种状况,编译器不容许在block中修改自动变量的值,不然报错。
为了解决这个问题有两种方法。第一种,将截获的自动变量改写成下列类型:
静态全局变量、全局变量这两种外部变量,由于其做用域是全局的,在block内能够直接访问。因此不须要截获。 静态局部变量自己在block语法的函数外,他是怎么作到能够修改值的,先来看下面代码:
int main(int argc, const char * argv[]) {
statc int val = 10
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
复制代码
转化后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;//编译时就自动生成了相应的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;//block的isa默认是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
可是实际使用中,咱们不多采用这种方案。由于block中能够存放超过变量做用域的自动变量,而当使用超过做用域的静态局部变量时,没法经过指针访问。
为了解决此类问题,咱们采用第二种方案"__block说明符",他又是怎么实现的呢?
咱们将上述代码改成:
int main(int argc, const char * argv[]) {
__block int val = 10
void (^blk)(void) = ^{
val = 1;
printf("val = %d", val);
};
blk();
return 0;
}
复制代码
转换后的代码:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 注意这里!!!!!
int __flags;
int __size;
int val;
};
/ * Block结构体 */
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // 注意这里!!!!!
构造函数
__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;
}
};
/ * Block方法 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // 注意这里!!!!!
(val->__forwarding->val) = 1;// 注意这里!!!!!
}
//捕获的变量的copy和release
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*/);}
//block的描述结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/ * __block int val = 0; 转换后代码*/
__Block_byref_val_0 val = {
0, //__isa
(__Block_byref_val_0 *)&val, // __forwarding 注意这里!!!!
0, // flag
sizeof(__Block_byref_val_0), // size
10 // 变量i
};
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DAYA, &val, 0x22000000);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
只是在自动变量上附加了__block说明符,源代码就急剧增长。咱们先来看看__block变量val是怎么转换的?
/ * __block int val = 0; 转换后代码*/
__Block_byref_val_0 val = {
0, //__isa
(__Block_byref_val_0 *)&val, // __forwarding 注意这里!!!!
0, // flag
sizeof(__Block_byref_val_0), // size
10 // 变量i
};
复制代码
咱们发现他居然变为告终构体实例,用__block修饰的变量变成了__Block_byref_val_0结构体类型,其定义以下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 注意这里!!!!!
int __flags;
int __size;
int val;
};
复制代码
经过block结构体的初始化,咱们能够看出,block捕获的实际是__Block_byref_val_0结构体的地址:
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DAYA, &val, 0x22000000);
复制代码
再来看看__block变量的赋值代码又是如何实现的?
//^{val = 1;}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // 注意这里!!!!!
(val->__forwarding->val) = 1;// 注意这里!!!!!
}
复制代码
那么问题来了,他是如何解决局部自动变量超出做用域后,还能正常使用的问题?为何要设计__forwarding?
捕获对象类型的变量和使用__block 修饰自动变量时,都在clang转换的代码中,看到了这样两个函数。简单来讲他们都是用来作block结构体变量的复制和释放的。
//捕获的变量的copy和release
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*/);}
复制代码
以__block修饰的自动变量具体,栈block经过copy复制到了了堆上。此时,block使用到的__block变量也会被复制到堆上并被block持有。若是是多个block使用了同一个__block变量,那么,有多少个block被复制到堆上,堆上的__block变量就被多少个block持有。当__block变量没有被任何block持有时(block被废弃了),它就会被释放。
栈上__block变量被复制到堆上后,会将成员变量__forwarding指针从指向本身换成指向堆上的__block,而堆上__block的__forwarding才是指向本身。
这样,无论__block变量是在栈上仍是在堆上,均可以经过__forwarding来访问到变量值。
总结:
最后,简单提下block循环引用,其产生循环引用的原理根普通循环引用同样,解决的办法也同样:
不在过多介绍。
参考文章:
Objective-c高级编程-ios与OS X多线程和内存管理
iOS Block原理探究以及循环引用的问题