简介
什么是Block?
Block是将函数及其执行上下文封装起来的对象。iOS4之后推出。C语言的扩展
void(^blockName)(参数列表) = ^(参数列表) {执行内容};
Block的类型
三种类型
全局block
不使用外部变量的block 或者 用到静态/全局变量。
_NSConcreteGlobalBlock,存储在数据区.data。
NSLog(@"%@",[^{
NSLog(@"globalBlock");
} class]);
控制台输出__NSGlobalBlock__。
栈block
使用了外部变量,但是没有进行过copy操作的block。
_NSConcreteStackBlock,存储在栈区。
NSInteger num = 10;
NSLog(@"%@",[^{
NSLog(@"stackBlock:%zd",num);
} class]);
控制台输出__NSStackBlock__。
堆block
对栈中的block进行过copy操作后,就是堆block。
_NSConcreteMallocBlock,存储在堆区。
怎样算copy操作?根据《Objective-C高级编程》,有以下4种情况。 1.调用copy 2.Block是函数的返回值 3.Block被强引用,Block被赋值给__strong或者id类型 4.方法名中含有usingBlock的Cocoa框架方法或GCD的API入参中含有usingBlcok的方法
上面的情况,系统会默认调用copy操作。
NSInteger num = 10;
void (^mallocBlock)(void) = ^{
NSLog(@"stackBlock:%zd",num);
};
NSLog(@"%@",[mallocBlock class]);
控制台输出__NSMallocBlock__。
但是对全局block进行copy,仍是全局block。
void (^globalBlock)(void) = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",[globalBlock class]);
控制台输出__NSGlobalBlock__。
对栈blockcopy之后,并不代表着栈block就消失了,左边的mallock是堆block,右边被copy的仍是栈block。
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block
{
block();
dispatch_block_t tempBlock = block;
NSLog(@"%@,%@",[block class],[tempBlock class]);
}
控制台输出__NSStackBlock__,__NSMallocBlock__。
返回block的场景。
NSLog(@"%@",[[self testReturnBlock] class]);
- (dispatch_block_t)testReturnBlock {
NSInteger testNum = 5;
return ^{NSLog(@"returnBlock%@",@(testNum));};
}
控制台输出为__NSMallocBlock__。
对栈Block进行copy,将会copy到堆区。
对堆Block进行copy,将会增加引用计数。
对全局Block进行copy,因为是已经初始化的,所以什么也不做。
Block的实现
block实际上是作为c语言源代码来处理的。
使用clang -rewrite-objc **.m转换为c++源代码(本质还是c语言源代码)
Block的结构
void(^blk)(void)=^{printf("aaa");};
blk();
转换后。
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("aaa");}
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[]) {
void(*blk)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
开始分析。将
void(*blk)(void)=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉类型转换后,得到
void(*blk)(void)=&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
通过构造函数,初始化blk,构造函数的入参为__main_block_func_0和__main_block_desc_0_DATA。
其中__main_block_func_0赋值给impl,这是一个__block_impl类型的结构体。保存了函数指针,isa等信息。查看__main_block_func_0,可以看到内容为block的执行内容。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void * ;
};
__main_block_desc_0_DATA则赋值给Desc,这是一个__main_block_desc_0结构体,里面存储了保留字段以及blokc的size。
我们再看下block的调用。
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
变换为
(*)blk->FuncPtr(blk)
使用函数指针调用函数。
理解了简单场景,下面开始增加难度,block的变量截取。
Block的变量截取
局部变量
NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%ld",block(2));
控制台输出为6,说明block中捕获的num值为3。我们转化看下。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSInteger num; //【多了一个成员变量num】
//【通过构造方法,捕获到num,具体值是多少,看main中block的初始化部分】
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself, NSInteger n) {
//【通过__cself,找到num】
NSInteger num = __cself->num; // bound by copy
return n*num;
}
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[]) {
NSInteger num = 3;
NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
num = 1;
((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);
return 0;
}
block的初始化,传入num的值,此时num的值为3,所以__main_block_impl_0中num的值为3。
调用block时,__main_block_func_0通过__cself,找到的num为3。
所以最终打印的为6。
如果我们在block内部,修改num的值呢?
此时编译器会报错,提示我们Variable is not assignable(****)。因为block捕获的仅仅是值,没有办法通过内部的修改,从而修改外部的值,对内部捕获到的值,只能是只读操作。
如果希望修改呢?
改为静态变量或全局变量或使用__block,接下来会介绍。
静态局部变量
static NSInteger num = 3;
NSInteger(^myBlock)(NSInteger) = ^NSInteger(NSInteger n){
return n*num;
};
num = 1;
NSLog(@"%ld",myBlock(2));
控制台打印2。查看转换后的源码。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSInteger *num;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger *_num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself, NSInteger n) {
NSInteger *num = __cself->num; // bound by copy
return n*(*num);
}
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[]) {
static NSInteger num = 3;
NSInteger(*myBlock)(NSInteger) = ((NSInteger (*)(NSInteger))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &num));
num = 1;
((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2);
return 0;
}
发现__main_block_impl_0中捕获的是静态变量的指针。在构造函数中,传递的入参为&num。在block中执行取值时,取到变量的指针,然后通过*取值。所以,后面修改num的值,会直接影响到block。
超出作用域使用变量。由Block语法生成的值Block,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃。这时将不能通过指针访问原来的自动变量。可以采用__block。后文介绍。
全局变量/静态全局变量
int num3 = 1;
static int num4 = 2;
int main(int argc, const char * argv[]) {
void(^myBlock)(void) = ^(){
printf("%d",num3);
printf("%d",num4);
};
myBlock();
return 0;
}
转换后查看。
int num3 = 1;
static int num4 = 2;
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("%d",num3);
printf("%d",num4);
}
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[]) {
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
可以看到,作用域广,直接使用值。不存在转换。
__block
int main(int argc, const char * argv[]) {
__block int val = 10;
void(^blk)(void) = ^{
val = 5;
};
return 0;
}
转换后。
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) = 5;
}
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(int argc, const char * argv[]) {
__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));
return 0;
}
发现,main中的val,被转换成了结构体__Block_byref_val_0,初始值为10。__forwarding的值是自己,也就是说,__forwarding指向了自己。再看下__main_block_func_0,里面的取值方式。如下图所示。

__block可以在block内部修改值,但是,为什么要有__forwarding?出现了copy和dispose方法?
这一切都是因为变量作用域的问题。
设置在栈上的block,会随着其所属的变量作用域的结束而结束,同时__block变量也会被废弃。
Blocks提供一种将栈上block复制到堆上block的方法来解决这个问题。即使block所属的变量作用域结束,堆上的block还可以继续存在,__block变量也不会受影响。
此时,__forwarding的作用体现出来了。
有时__block变量是配置在堆上,我们想访问栈上的__block变量。只要栈上的__forwarding指向的是堆上的,就能正确访问到。无论__block变量是在栈还是在堆,都可以通过__forwarding访问到正确的__block变量。
什么时候复制到堆上? 之前在block类型中指出了4种场景,那4种场景系统已经帮我们调用了copy,什么情况下我们需要手动呢?
向 方法或函数 的参数中 传递 block的时候。

堆上的block----copy,仅仅是引用计数的增加
借图。


截获对象
上面的例子中,截获的都是数值。如果是对象呢?
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
void(^block)(void) = ^{
NSLog(@"%@",arr);
[arr addObject:@"4"];
};
[arr addObject:@"3"];
block();
代码正常执行,1,2,3。转化一下。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *arr;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_arr, int flags=0) : arr(_arr) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSMutableArray *arr = __cself->arr; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ld_6b0zns190957flvr5h_6vgwc0000gn_T_block_97ed65_i_2,arr);
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)arr, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_ld_6b0zns190957flvr5h_6vgwc0000gn_T_block_97ed65_i_3);
}
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};
int main(int argc, const char * argv[]) {
NSMutableArray *arr = ((NSMutableArray * _Nonnull (*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("arrayWithObjects:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_ld_6b0zns190957flvr5h_6vgwc0000gn_T_block_97ed65_i_0, (NSString *)&__NSConstantStringImpl__var_folders_ld_6b0zns190957flvr5h_6vgwc0000gn_T_block_97ed65_i_1, __null);
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, arr, 570425344));
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)arr, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_ld_6b0zns190957flvr5h_6vgwc0000gn_T_block_97ed65_i_4);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
发现此处有 copy和dispose方法。为什么?涉及到内存管理。
首先,arr是个对象,默认为strong类型,C语言的机构体中是不能含有__strong修饰符的变量,编译器是不清楚何时对结构体进行初始化和废弃,不能很好的管理内存。
于是,oc的runtime机制出场了(强大的runtime),它能够准确把握栈block复制到堆上的时机,为此需要增加__main_block_impl_0成员变量的__main_block_copy_0和__main_block_dispose_0。
如果我们对对象增加__block修饰符呢?
struct __Block_byref_arr_0 {
void *__isa;
__Block_byref_arr_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *arr;
};
循环引用
self持有block,block内部使用到self。
解决方案weak以下self。
有时我们会看到在block内部会strong一下,为什么呢?
weak的原理我们知道,一个对象的释放,会引起指向它的弱引用指针置位nil。
如果我们在block内部执行一段延迟方法。block调用完成后结束,作用域结束,对象释放,对象的释放,引起weakself的释放。在延迟方法中再使用weakself,此时是nil。所以,我们会在block外部weak一下,block内部strong一下,防止提前释放。
总结
block只会捕获到它内部使用到的变量,使用不到的变量,不会捕获。
想要修改捕获到的值,可以通过两种方式,一种是改变作用域,一种是通过传递内存指针。
注意循环引用的问题。
为什么会有__block?
是为了可以在block中修改外部变量值。原理是通过__block,将变量转换为结构体,通过指针的方式。
在ARC下,我们经常见到的是堆block,是因为我们通过=将block赋值给了blk变量,而blk默认是strong修饰符。