1.Block 的使用
Block 是什么?
块,封装了函数调用以及调用环境的 OC 对象,
Block 的声明
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 返回值类型(^block变量名)(参数1类型,参数2类型,...)
void(^block)(void);
Block 的定义
// ^返回值类型(参数1,参数2,...){};
// 1.无返回值,无参数
void(^block1)(void) = ^{
};
// 2.无返回值,有参数
void(^block2)(int) = ^(int a){
};
// 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
int(^block3)(void) = ^int{
return 3;
};
// 以上Block的定义也可以这样写:
int(^block4)(void) = ^{
return 3;
};
// 4.有返回值,有参数
int(^block5)(int) = ^int(int a){
return 3 * a;
};
Block 的调用
// 1.无返回值,无参数
block1();
// 2.有返回值,有参数
int a = block5(2);
使用示例
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
Block 的 Code Snippets 快捷方式
2.Block的底层数据结构
- Block 本质上也是一个 OC 对象,它内部也有个
isa
指针; - Block 是封装了函数调用以及调用环境的 OC 对象;
- Block 的底层数据结构如下图所示:
通过 Clang 将以下 Block 代码转换为 C++ 代码,来分析 Block 的底层实现。
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"调用了block");
};
block();
}
return 0;
}
- Block 底层数据结构就是一个
__main_block_impl_0
结构体对象,其中有__block_impl
和__main_block_desc_0
两个结构体对象成员。
main:表示 block 所在的函数
block:表示这个一个 block
impl:表示实现(implementation)
0:表示这是该函数中的第一个 block __main_block_func_0
结构体封装了 block 里的代码;__block_impl
结构体才是真正定义 block 的结构,其中的FuncPtr
指针指向__main_block_func_0
;__main_block_desc_0
是 block 的描述对象,存储着 block 的内存大小等;- 定义 block 的本质:
调用__main_block_impl_0()
构造函数,并且给它传了两个参数__main_block_func_0
和&__main_block_desc_0_DATA
。拿到函数的返回值,再取返回值的地址&__main_block_impl_0
,把这个地址赋值给 block 变量。 - 调用 block 的本质:
通过__main_block_impl_0
中的__block_impl
中的FuncPtr
拿到函数地址,直接调用。
// main.cpp
struct __main_block_impl_0 {
struct __block_impl impl; // block的结构体
struct __main_block_desc_0* Desc; // block的描述对象,描述block的大小等
/* 构造函数
** 返回值:__main_block_impl_0 结构体
** 参数一:__main_block_func_0 结构体
** 参数二:__main_block_desc_0 结构体的地址
** 参数三:flags 标识位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //_NSConcreteStackBlock 表示block存在栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// __main_block_func_0 封装了block里的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_58a448_mi_0);
}
struct __block_impl {
void *isa; // block的类型
int Flags; // 标识位
int Reserved; //
void *FuncPtr; // block的执行函数指针,指向__main_block_func_0
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block本质结构体所占内存空间
} __main_block_desc_0_DATA = {
0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
/*
** void(^block)(void) = ^{
NSLog(@"调用了block");
};
** 定义block的本质:
** 调用__main_block_impl_0()构造函数
** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
** __main_block_func_0 封装了block里的代码
** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
** 把这个地址赋值给 block
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
));
/*
** block();
** 调用block的本质:
** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
3.Block的变量捕获机制
为了保证 block 内部能够正常访问外部的变量,block 有个变量捕获机制。
- 对于全局变量,
不会捕获
到 block 内部,访问方式为直接访问
; - 对于 auto 类型的局部变量,
会捕获
到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的值,访问方式为值传递
; - 对于 static 类型的局部变量,
会捕获
到 block 内部,block 内部会自动生成一个成员变量,用来存储这个变量的地址,访问方式为指针传递
; - 对于对象类型的局部变量,block 会
连同它的所有权修饰符一起捕获
。
3.1 auto 类型的局部变量
auto 自动变量:我们定义出来的变量,默认都是 auto 类型,只是省略了。
auto int age = 10;
auto 类型的局部变量会捕获
到 block 内部,访问方式为值传递
。
通过 Clang 将以下代码转换为 C++ 代码:
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
__main_block_impl_0
对象内部会生成一个相同的age
变量;__main_block_impl_0()
构造函数多了个参数,用来捕获访问的外面的age
变量的值
,将它赋值给__main_block_impl_0
对象内部的age
变量。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_77_f_d18dtx6277bxbcd8s72my80000gn_T_main_5ed490_mi_0,age);
}
......
int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
由于是值传递
,我们修改外部的age
变量的值,不会影响到 block 内部的age
变量。
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 10
3.2 static 类型的局部变量
static 类型的局部变量会捕获
到 block 内部,访问方式为