Block做为Objective-C中闭包的实如今iOS开发中占有很是重要的地位,尤为是做为回调(callback)使用。这篇文章主要记录Block的实现,关于Block的语法能够参考这里:How Do I Declare A Block in Objective-Chtml
Block被称为带有自动变量(局部变量)的匿名函数,Block语法去和C语言的函数很是类似。实际上Block的底层就是做为C语言源代码来处理的,支持Block的编译器会将含有Block语法的源代码转换为C语言编译器能处理的源代码,看成C语言源码来编译。编程
经过LLVM编译器clang能够将含有Block的语法转换为C++源码:bash
clang -rewrite-objc fileName
复制代码
好比一段很是简单的含有Block的代码:数据结构
#include <stdio.h>
int main() {
void (^blk)(void) = ^{
printf("Hello Block!\n");
};
blk();
return 0;
}
复制代码
使用clang将其转换为C++源码后,其核心内容以下:多线程
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 Block!\n");
}
// 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() {
// 调用__main_block_impl_0的构造函数
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// blk()调用
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
这段源码主要包含了3个struct和两个函数,实际上就是C语言源码:闭包
很容易看出mian()函数就是最初代码中的mian函数,__main_block_func_0函数就是最初代码中的Block语法:app
^{
printf("Hello Block!\n");
};
复制代码
由此得出:框架
接下来重点看看__main_block_impl_0结构体函数
__main_block_func_0函数的参数__cself类型声明为struct __main_block_impl_0。__main_block_impl_0就是该Block的数据结构定义,其中包含了成员变量为impl和Desc指针,impl的__block_impl结构体声明中包含了某些标志、从此版本升级所需的区域以及函数指针:ui
struct __block_impl {
void *isa;
int Flags; // 某些标志
int Reserved; // 从此版本升级所需的区域
void *FuncPtr; // 函数指针
};
复制代码
Desc指针的中包含了Block的大小:
static struct __main_block_desc_0 {
size_t reserved; // 从此版本升级所需的区域
size_t Block_size; // Block的大小
};
复制代码
在__main_block_impl_0的构造函数中调用了impl和Desc的成员变量,这个构造函数在mian函数中被调用,为了便于阅读,将其中的转换去掉:
// 调用__main_block_impl_0的构造函数
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 去掉转换以后
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_func_0 *blk = &tmp;
复制代码
构造函数中使用的实参为函数指针__main_block_func_0和静态全局变量初始化的__main_block_desc_0结构体实例指针__main_block_desc_0_DATA:
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
复制代码
经过这些调用能够总结出下面两条:
将__main_block_impl_0结构体展开:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
};
复制代码
在该程序中构造函数的初始化数据以下:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
复制代码
能够看出FuncPtr = __main_block_func_0就是简单的使用函数指针FuncPtr调用函数__main_block_func_0打印Hello Block!语句,这就是最初的源码中对于block调用的实现:
blk();
复制代码
其对应的源码去掉转换以后就很清晰:
// blk()调用
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// 去掉转换以后:
(*blk->impl.FuncPtr)(blk);
复制代码
到此,对于Block的建立和使用就能够这样理解:
在__main_block_impl_0的构造函数中有一个_NSConcreteStackBlock:
impl.isa = &_NSConcreteStackBlock;
复制代码
这个isa指针很容易想到Objective-C中的isa指针。在Objective-C类和对象中,每一个对象都有一个isa指针,Objective中的类最终转换为struct,类中的成员变量会被声明为结构体成员,各种的结构体是基于objc_class结构体的class_t结构体。
typedef struct Objc_object {
Class isa;
} *id;
typedef struct obje_class *Class;
struct objc_class {
Class isa;
};
struct class_t {
struct class_t *isa;
strcut class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
};
复制代码
实际上Objective-C中由类生成对象就是像结构体这样生成该类生成的对象的结构体实例。生成的各个对象(即由该生成的对象的各个结构体实例),经过成员变量isa保持该类的结构体实例指针。
好比一个具备成员变量valueA和valueB的TestObject类:
@interface TestObject : NSObject {
int valueA;
int valueB;
}
@end
复制代码
其类的对象的结构体以下:
struct TestObject{
Class isa;
int valueA;
int valueB;
};
复制代码
**在Objective-C中,每一个类(好比NSObject、NSMutableArray)均生成并保持各个类的class_t结构体实例。**该实例持有声明的成员变量、方法名称、方法的实现(即函数指针)、属性以及父类的指针,并被Objective-C运行时库所使用。
再看__main_block_impl_0结构体就至关于基于Objc_object的结构体的Objective-C类对象的结构体,其中的成员变量isa初始化为isa = &_NSConcreteStackBlock;
,_NSConcreteStackBlock就至关于calss_t结构体实例,在将Block做为Objective-C对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。
实际上Block的实质Block就是Objective-C对象。
Block做为传统回调函数的替代方法的其中一个缘由是:block容许访问局部变量,能捕获所使用的变量的值,即保存该自动变量的瞬间值,好比下面这段代码,Block中保存了局部变量mul的瞬间值7,因此后面对于mul的更改不影响Block中保存的mul值:
int mul = 7;
int (^blk)(int) = ^(int num) {
return mul * num;
};
// change mul
mul = 10;
int res = blk(3);
NSLog(@"res:%d", res); // res:21 not 30
复制代码
经过clang来看看Block捕获自动变量以后Block的结构有什么变化:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int mul; // Block语法表达式中使用的自动变量被看成成员变量追加到了__main_block_impl_0结构体中
// 初始化结构体实例时,根据传递构造函数的参数对由自动变变量追加的成员变量进行初始化
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _mul, int flags=0) : mul(_mul) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int mul = __cself->mul; // bound by copy
printf("mul is:%d\n", mul);
}
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 mul = 7;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, mul));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
复制代码
分析__main_block_impl_0和其构造方法能够发现:Block中使用的自动变量mul被看成成员变量追加到了__main_block_impl_0结构体中,并根据传递构造函数的参数对该成员变量进行初始化。__main_block_impl_0中结构体实例的初始化以下:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
mul = 7; // 追加的成员变量
复制代码
因而可知,**在__main_block_impl_0结构体实例(即Block)中,自动变量值被捕获。**能够将Block捕获自动变量总结为以下:Block在执行语法时,Block中所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。即向结构体__main_block_impl_0中追加成员变量。
虽然Block能捕获自动变量值,可是却不能对其进行修改,好比下面代码就会报错:
int main() {
int val = 10;
void(^blk)(void) = ^ {
val = 1; // error Variable is not assignable (missing __block type specifier)
};
blk();
printf("val:%d\n", val);
return 0;
}
复制代码
需对val变量使用__block说明符:
int main() {
__block int val = 10;
void(^blk)(void) = ^ {
val = 1;
};
blk();
printf("val:%d\n", val); // val:1
return 0;
}
复制代码
将其用clang转换以后:
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) = 1;
}
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*/);
}
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() {
// __block类型的变量竟然变成告终构体 __block int val = 10;
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
printf("val:%d\n", (val.__forwarding->val));
return 0;
}
复制代码
增长了__block变量以后源码急剧增多,最明显的是增长了一个结构体和4个函数:
首先比较一下使用__block和没有使用__block的__main_block_func_0函数对变化
// 没有使用__block
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int mul = __cself->mul; // bound by copy
printf("mul is:%d\n", mul);
}
// 使用__block
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) = 1;
}
复制代码
能够看出在没有使用__block时,Block仅仅是捕获自动变量的值,即int mul = __cself->mul;
。
再看刚才的源码,使用__block变量的val竟然变成告终构体实例。
// __block int val = 10; 转换以后的源码:
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
复制代码
__block变量也同Block同样变成了__Block_byref_val_0结构体类型的自动变量(栈上生成的__Block_byref_val_0结构体实例),该变量初始化为10,且这个值也出如今结构体实例的初始化中,表示该结构体持有至关于原有自动变量的成员变量(下面__Block_byref_val_0结构体中的成员变量val就是至关于原自动变量的成员变量):
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; // 至关于原自动变量的成员变量
};
复制代码
回过头去看Block给val变量赋值的代码:
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) = 1;
}
复制代码
得出:__Block_byref_val_0结构体实例的成员变量__forwarding持有指向实例自身的指针。经过成员变量__forwarding访问成员变量val。
这里没有将__block变量的__Block_byref_val_0结构体直接写在Block的__main_block_impl_0结构体中是为了能在多个Block中使用同一个__block变量。 好比在两个Block中使用同一个__block变量:
__block int val = 10;
void (^blk1)(void) = ^ {
val = 1;
};
void (^blk2)(void) = ^ {
val = 2;
};
复制代码
转换以后:
__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof( __Block_byref_val_0), 10};
blk1 = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 570425344);
blk2 = &__main_block_impl_1(__main_block_func_1, &__main_block_desc_1_DATA, &val, 570425344);
复制代码
虽然到这里已经大体知道为何Block能捕获自动变量了,可是这里还遗留几个问题:
综上可知:
结构体类型的自动变量即栈上说生成的该结构体的实例。
既然Block是Objective-C对象,那么它具体是哪一种对象?在Block中的isa指针指向的就是该Block的Class,目前所见都是_NSConcreteStackBlock类型,而在block的runtime中实际定义了6中类型的Block,其中咱们主要接触到的是这三种:
它们对应在程序中的内存分配:
那么Block在什么状况下时在堆上的?何时时栈上的?何时又是全局的?
_NSConcreteGlobalBlock很好理解,将Block看成全局变量使用的时候,生成的Block就是_NSConcreteGlobalBlock类对象。好比:
#include <stdio.h>
void (^blk)(void) = ^{
printf("Gloabl Block\n");
};
int main() {
return 0;
}
复制代码
用clang转换以后为该Block用结构体__block_impl的成员变量初始化为_NSConcreteGlobalBlock,即Block用结构体实例设置在程序内存的数据区:
isa = &_NSConcreteGlobalBlock;
复制代码
将全局Block存放在数据区的原为:**使用全局变量的地方不能使用自动变量,因此不存在对自动变量的捕获。所以Block用结构体实例的内容不依赖于执行时的状态,因此整个程序中只须要一个实例。**只有在捕获自动变量时,Block用结构体实例捕获的值才会根据执行时的状态变化。所以总结Block为_NSConcreteGlobalBlock类对象的状况以下:
除了上述两中状况下Block配置在程序的数据区中之外,Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。 配置在栈上的Block,若是其所属的变量做用域结束,该Block就被自动废弃。
那么配置在堆上的_NSConcreteMallocBlock类在什么时候使用?
配置在全局变量上的Block,从变量做用域外也能够经过指针访问。可是设置在栈上的Block,若是其所属的做用域结束,该Block就被废弃;而且__block变量的也是配置在栈上的,若是其所属的变量做用域结束,则该__block变量也会被废弃。那么这时须要将Block和__block变量复制到堆上,才能让其不受变量域做用结束的影响。
Block提供了将Block和__block变量从栈上复制到堆上的方法。复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa:
isa = &_NSConcreteMallocBlock;
复制代码
对于堆上的__block的访问,就是经过__forwarding实现的:**__block变量用结构体成员变量__forwarding实现不管__block变量配置在栈仍是在堆上都能正确的访问__block变量。**当__block变量配置在堆上时,只要栈上的结构体成员变量__forwarding指向堆上的结构体实例,那么不论是从栈上仍是从堆上的__block变量都能正确访问。
而且在ARC时期,大多数状况下编译器知道在合适自动将Block从栈上复制到堆上,好比将Block做为返回值时。而当向方法或函数的参数中传递Block时,编译器不能判断,须要手动调用copy方法将栈上的Block复制到堆上,可是apple提供的一些方法已经在内部恰当的地方复制了传递过来的参数,这种状况就不须要再手动复制:
而且,无论Block配置在何存,用copy方法复制都不会出现问题。可是将Block从栈上复制到堆上时至关消耗CPU的。对于已经在堆上的Block调用copy方法,会增长其引用计数。
而且对使用__block变量的Block从栈复制到堆上时,__block变量也会收到影响:若是在1个Block中使用__block变量,当该Block从栈复制到堆时,这些__block变量也所有被从栈复制到堆上。而且此时Block持有__block变量。若是有个Block使用__block变量,在任何一个Block从栈复制到堆时,__block变量都会一并复制到堆上并被该Block持有;当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增长其引用计数。若是配置在堆上的Block被废弃,它说使用的__block变量也就被释放。这种思考方式同Objective-C内存管理方式相同。即使用__block变量的Block持有该__block变量,当Block被废弃时,它所持有的__block变量也被废弃。
在这里Block捕获的是__block类型的变量val,若是捕获的是Objective-C对象会有什么区别?
int main() {
id arr = [[NSMutableArray alloc] init];
void (^blk)(id) = [^(id obj) {
[arr addObject:obj];
NSLog(@"arr count: %ld", [arr count]);
} copy];
blk(@"Objective-C");
blk(@"Switf");
blk(@"C++");
return 0;
}
复制代码
值得注意的是:Block捕获的是objective-C对象,而且调用变动该对象的方法addObject:,因此这里不会产生编译错误。这是由于block捕获的变量值是一个NSMutableArray类的对象,用C语言描述就是捕获NSMutableArray类对象用的结构体实例指针。addObject方法是使用block截获的自动变量arr的值,因此不会有任何问题,可是若是在Block内部去给捕获的arr对象赋值就会出错:
int main() {
id arr = [[NSMutableArray alloc] init];
void (^blk)(id) = [^(id obj) {
arr = [NSMutableArray arrayWithObjects:obj, nil]; // error Variable is not assignable (missing __block type specifier)
NSLog(@"arr count: %ld", [arr count]);
} copy];
blk(@"Objective-C");
return 0;
}
复制代码
以前的代码转换以后的部分源码为:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->arr, (void*)src->arr, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->arr, 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 int val = 10;
转换以后的源码中的部份内容:
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*/);
}
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*);
}
复制代码
OBjective-C对象和__block变量对比,发如今Block用的结构体部分基本相同,不一样之处在于:Objective-C对象用BLOCK_FIELD_IS_OBJECT标识,__block变量是用BLOCK_FIELD_IS_BYREF标识。即通过BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF参数区分copy函数和dispose函数的对象类型是对象仍是__block变量。
该源码中在__main_block_desc_0 结构体中增长了成员变量copy和dispose,以及做为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数,这两个函数的做用:
__main_block_copy_0函数中所使用的_Block_object_assign函数将对象类型对象复制给Block用结构体的成员变量arr并持有该对象,调用_Block_object_assign函数至关于retain函数,将对象赋值在对象类型的结构体成员变量中。
__main_block_dispose_0函数中使用_Block_object_dispose函数释放赋值在Block用结构体成员变量arr中的对象。调用_Block_object_dispose函数至关于调用release函数,释放赋值在对象类型结构体中的对象。
这两个函数在Block从栈复制到堆和已经堆上的Block被废弃时调用:
当Block从栈复制到堆上时,Block会持有捕获的对象,这样就容易产生循环引用。好比在self中引用了Block,Block优捕获了self,就会引发循环引用,编译器一般能检测出这种循环引用:
@interface TestObject : NSObject
@property(nonatomic, copy) void (^blk)(void);
@end
@implementation TestObject
- (instancetype)init {
self = [super init];
if (self) {
self.blk = ^{
NSLog(@"%@", self); // warning:Capturing 'self' strongly in this block is likely to lead to a retain cycle
};
}
return self;
}
复制代码
一样,若是捕获到的是当前对象的成员变量对象,一样也会形成对self的引用,好比下面的代码,Block使用了self对象的的成员变量name,实际上就是捕获了self,对于编译器来讲name只不过期对象用结构体的成员变量:
@interface TestObject : NSObject
@property(nonatomic, copy) void (^blk)(void);
@property(nonatomic, copy) NSString *name;
@end
@implementation TestObject
- (instancetype)init {
self = [super init];
if (self) {
self.blk = ^{
NSLog(@"%@", self.name);
};
}
return self;
}
@end
复制代码
解决循环引用的方法有两种:
使用__weak来声明self
- (instancetype)init {
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"%@", weakSelf.name);
};
}
return self;
}
复制代码
使用临时变量来避免引用self
- (instancetype)init {
self = [super init];
if (self) {
id tmp = self.name;
self.blk = ^{
NSLog(@"%@", tmp);
};
}
return self;
}
复制代码
使用__weak修饰符修饰对象以后,在Block中对对象就是弱引用: