Blocks

什么是Blocks
Block
是 C
语言的扩充功能:带有自动变量的匿名函数。
- 自动变量: 局部变量
- 匿名函数: 不带有名称的函数
C
语言的标准库中不允许存在这样的函数。
函数调用方式
- 使用函数名称调用函数
- 使用函数指针调用函数
// 函数声明
int func(int count);
// 使用函数名称调用函数
int result = func(10);
// 使用函数指针调用函数
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);
&
取地址*
取值
C 语言的函数中可能使用的变量:
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中,在函数的多次调用之间能够传递值的变量有:
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
C++ 和 Objective-C 使用类可以保持变量值且能够多次持有该变量自身。
但是声明和实现 C++ 和 Objective-C 的类增加了代码长度,这事就需要使用 Blocks了; Blocks 提供了类似由 c++ 和 Objective-C 类生成实例或对象来保持变量值的方法。
Blocks 模式
Block 语法
完整形式的 Block
语法与一般的 C 语言函数定义相比,仅有两点不同:
- 没有函数名
- 带有
^
C 语言函数定义如下:
返回值类型
函数名称
参数列表
表达式
Block 语法格式如下:
^
返回值类型
参数列表
表达式
省略返回值后的格式如下:
^
参数列表
表达式
同时省略返回值和参数列表的格式如下:
^
表达式
Block 类型变量
在定义 C 语言函数时,可以将所定义的函数的地址赋值给函数指针类型变量中:
int func(int count) {
return count + 1;
}
int (*funcptr)(int) = &func;
这样一来,函数 func
的地址就能赋值给函数指针类型变量 funcptr
中了。
同样的,在 Block 语法下,可将 Block 语法赋值给声明为 Block 类型的变量中。
int (^blk)(int);
声明 Block 类型变量仅仅是将声明函数指针类型变量的 *
变为 ^
。
Block 类型的变量的用途:
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
使用 Block 语法将 Block 赋值为 Block 类型变量示例如下:
int (^blk)(int) = ^(int count) { return count + 1; };
因为与通常的变量相同,所以可以由 Block 类型变量向 Block 类型变量赋值:
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk;
在函数参数中使用 Block 类型变量向函数传递 Block:
void func(int (^blk)(int)) {
}
将 Block 作为函数的返回值:
int (^func())(int) {
return ^(int count) { return count + 1; };
}
由上可知,在函数参数和返回值中使用的 Block 类型变量时,记述方式极为复杂。 这时,可以像使用函数指针类型时那样,使用 typedef 来解决该问题:
typedef int (^blk_t)(int);
// 作为函数参数
void func1(blk_t blk) {
}
// 作为函数返回值
blk_t func2() {
return ^(int count) { return count + 1; };
}
截获自动变量值
“带有自动变量值”在 Blocks 中表现为“截获自动变量值”。
void autoGetValueForBlock() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^ { printf(fmt, val); } ;
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
}
Block 语法的表达式使用的它之前声明的自动变量 fmt
和 val
。
Blocks 中,Block 表达式所使用的自动变量的值,即保存该自动变量的瞬间值。
因为 Block 表达式保持了自动变量的值,所以在执行 Block 语法后,即使改写 Block 中使用的自动变量的值也不会影响执行时自动变量的值。
执行结果:
val = 10
__block 说明符
自动变量值截获只能保存执行 Block 语法瞬间的值。 保存后就不能改写该值。
尝试改写截获的自动变量的值:
int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n", val);
该代码会产生编译错误:
Variable is not assignable (missing __block type specifier)
若想在 Block 语法的表达式中将值赋给在 Block 语法外声明的自动变量, 需要在该自动变量上附加 __block
说明符。
__block int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n", val);
执行结果:
val = 1
截获的自动变量
将值赋值给 Block 中截获的自动变量,会产生编译错误;
对于截获的 Objective-C 对象,调用变更对象的方法不会产生编译错误。
NSMutableArray *array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^ {
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
该代码中截获的变量值为 NSMutableArray 类的对象。如果用 C 语言来描述,即是截获 NSMutableArray 类对象用的结构体实例指针。
虽然赋值给截获的自动变量 array 的操作会产生编译错误,但使用截获的值却不会有任何问题。
Blocks 的实现
Block 的实质
Block 语法看上去好像很特别,但它实际上是作为极普通的 C 语言源代码来处理的。 通过支持 Block 的编译器,含有 Block 语法的源代码转换为一般 C 语言编译器能够处理的源代码,并作为极为普通的 C 语言源代码被编译。
clang(LLVM 编译器)具有转换为我们可读源代码的功能。
通过 -rewrite-objc
选项就能将含有 Block 语法的源代码变换为 C++ 的源代码。
说是 C++,其实也仅是使用了 struct 结构,其本质是 C 语言源代码。
clang -rewrite-objc 源代码文件名
待转换的 Block 源代码:
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{ printf("Block\n"); };
blk();
return 0;
}
转换后的代码(部分):
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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("Block\n"); }
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;
}
所谓 Block 就是 Objective-C 对象
截获自动变量值
待转换的 Block 源代码:
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^ { printf(fmt, val); } ;
blk();
return 0;
}
转换后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val); }
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[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val)) ;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
所谓 “截获自动变量值” 意味着在执行 Block 语法时, Block 语法表达式所使用的自动变量值被保存到 Block 的结构体实例(即 Block 自身)中
__block 说明符
__block 存储域类说明符 (__block storage-class-specifier)
C 语言中有以下存储域类说明符:
- typedef
- extern
- static
- auto
- register
__block
说明符类似于 static
、auto
和 register
说明符,它们用于指定将变量值设置到哪个存储域中。
例如: auto
表示作为自动变量存储在栈中,
static
表示作为静态变量存储在数据区中。
待转换的 Block 源代码:
int main(int argc, const char * argv[]) {
__block int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n", val);
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) = 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(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), 0};
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
变量也同 Block
一样变成 __Block_byref_val_0
结构体类型的自动变量,即栈上生成的 __Block_byref_val_0
结构体实例。
__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_byref_val_0
结构体实例变量 __forwarding
持有指向该实例自身的指针。
通过成员变量 __forwarding
访问成员变量 val
。

Block 存储域
- Block 转换为 Block 的结构体类型的自动变量
- __block 变量转换为 __block 变量的结构体类型的自动变量
所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
名称 | 实质 |
---|---|
Block | 栈上 Block 的结构体实例 |
__block 变量 | 栈上 __block 变量的结构体实例 |
Block
也是 Objective-C
的对象。将 Block
当作 Objective-C
对象来看时,该 Block
的类为 _NSConcreteStakBlock
。
类 | 设置对象的存储域 |
---|---|
_NSConcreteStakBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data 区) |
_NSConcreteMallocBlock | 堆 |
应用程序的内存分配:
------------------
| |
| (程序区域) |
| .text |
| |
|------------------|
| |
| (数据区域) |
| .data |
| |
|------------------|
| |
| (堆) |
| |
|------------------|
| |
| (栈) |
| |
------------------
在记述全局变量的地方使用 Block 语法时,生成的 Block 为 _NSConcreteGlobalBlock
类对象。
因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量进行截获。由此,Block 用结构体实例的内容不依赖与执行时的状态,所以在整个程序中只需一个实例。
因此将 Block 用结构体实例设置在与全局变量相同的数据区域中即可。
只要 Block 不截获自动变量,就可以将 Block 用结构体实例设置在程序的数据区域
配置在全局变量上的 Block, 从变量作用域外也可以通过指针安全地使用。 但设置在栈上的 Block, 如果其所属的变量作用域结束,该 Block 就被废弃。 由于 __block 变量也配置在栈上,同样地,如果其所属的变量作用域结束,则该 __block 变量也会被放弃。
Blocks 提供了将 Block 和 __block 变量从栈上复制到堆上的方法来解决这个问题
将配置在栈上的 Block 复制到堆上,这样即使 Block 语法记述的变量作用域结束,堆上的 Block 还可以继续存在。

复制到堆上的 Block 将 _NSConcreteMallocBlock
类对象写入 Block 用结构体实例的成员变量 isa
。
imp.isa = &_NSConcreteMallocBlock;
而 __block
变量用结构体成员变量 __forwarding
可以实现 无论 __block
变量配置在栈上还是堆上时都能够正确地访问 __block
变量。
当 ARC 有效时,大多数情况下编译器会恰当的进行判断,自动生成将 Block 从栈上复制到堆上的代码。
在此之外的情况下,我们可以使用 copy实例方法
,手动将 Block 从栈上复制到堆上。
- (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"blk0:%d", val);} copy],
[^{NSLog(@"blk1:%d", val);} copy],nil];
}
对于 Block 语法可直接调用 copy
方法, 当然对于 Block 类型变量也可以调用 copy
方法:
void (^blk)(void) = ^{ printf("Block\n"); };
blk = [blk copy];
Block 类调用 copy 所产生的效果:
Block 的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStakBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 生么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
多次调用 copy
方法也不会存在问题
__block 变量存储域
使用 __block 变量的 Block 从栈复制到堆上时, __block 变量也会受到影响。
__block 变量的配置存储域 | Block 从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被 Block 持有 |
堆 | 被 Block 持有 |
在多个 Block 中使用 __block 变量时,因为最先会将所有的 Block 配置在栈上,所以 __block 变量也会配置在栈上。 在任何一个 Block 从栈复制到堆时,__block 变量也会一并从栈复制到堆并被该 Block 所持有。 当剩下的 Block 从栈复制到堆时,被复制的 Block 持有 __block 变量,并增加 __block 变量的引用计数。
通过 __forwarding
可以实现不论在 Block 语法中、 Block 语法外使用 __block 变量,还是 __block 变量配置在栈上或堆上,都可以顺利访问同一个 __block 变量。

截获对象
为了在附有 __strong
和 __weak
修饰符的变量,也可以恰当的进行初始化和废弃。为此需要在 __main_block_desc_0
结构体中增加成员变量 copy
和 dispose
,以及作为指针赋值给该成员变量的 __main_block_copy_0
和 __main_block_dispose_0
函数。
待转换的 Block 源代码:
blk_t blk;
{
NSMutableArray *array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %zd", [array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
转换后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
NSMutableArray *array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_m1j1pblx2kj64rpyd701tt3c0000gn_T_main_85c1bf_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
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
};
__main_block_copy_0
函数使用 _Block_object_assign
函数将该对象类型对象赋值给 Block 用结构体的成员变量 array 中并持有该对象。
_Block_object_assign
函数调用相当于 retain
实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
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*/);
}
__main_block_dispose_0
函数使用 _Block_object_dispose
函数,释放赋值在 Block 用结构体成员变量 array 中的对象。
_Block_object_dispose
函数调用相当于 release 实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
copy
函数 和 dispose
函数调用时机:
函数 | 调用时机 |
---|---|
copy 函数 | 栈上的 Block 复制到堆时 |
dispose 函数 | 堆上的 Block 被废弃时 |
栈上的 Block 复制到堆上的情况:
- 调用 Block 的
copy
实例方法时; - Block 作为函数返回值返回时;
- 将 Block 赋值给附有
__strong
修饰符id
类型的类或 Block 类型成员变量时; - 在方法名中含有
usingBlock
的Cocoa
框架方法或Grand Central Dispatch
的API
中传递 Block 时。
Block 循环引用
在 Block 中使用附有 __strong 修饰符的对象类型自动变量,当 Block 从栈复制到堆时,该对象为 Block 所持有。这样容易引起循环引用。
typedef void(^blk_t)(void);
@interface MyObject : NSObject {
blk_t blk_;
}
@end
@implementation MyObject
- (instancetype)init {
self = [super init];
blk_ = ^{ NSLog(@"self = %@", self); };
return self;
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
MyObject
类对象的 Block 类型成员变量 blk_
持有赋值为 Block 的强引用。init
实例方法中执行的 Block 语法使用附有 __strong
修饰符的 id
类型变量 self
。并且由于 Block 语法赋值在了成员变量 blk_
,因此通过 Block 语法生成在栈上的 Block 此时右栈复制到堆,并持有所使用的的 self。
self
持有 Block,Block 持有 self
,因此造成循环引用。