学习笔记,如有错误,欢迎批评指正!!! 仅供学习交流...
block的本质
- block是带有自动变量(局部变量)的匿名函数(不带名称的函数)
- 带有自动变量的值:block保持自动变量的值。
- block的本质是一个OC对象,它内部有一个isa指针。
直接上代码,查看block的底层结构
void (^block1)(void) = ^{
NSLog(@"This is block!!!");
};
接下来,使用终端命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o xxx.cpp
然后,查看xxx.cpp文件
,如下图所示(请忽略文件命名 🤣...)
接下来,我们把重点代码复制下来
//block转化成c++的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数(类似于OC的init方法)
__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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zt_xy2f_gsx0mlbrzmvts93gjqm0000gn_T_main_b2615a_mi_0);
}
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//定义block变量
void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
}
return 0;
}
对 main 函数里边的代码 进行简化:
//定义block变量
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
//执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
继续简化:
//定义block变量
//调用 __main_block_impl_0 构造函数,返回结构体对象,最后取地址,复制给 block1
//返回的结构体对象就是 struct __main_block_impl_0
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
//执行block内部的代码
block1->FuncPtr(block1);
block的底层机构大致就是这样的:
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;
//构造函数(类似于OC的init方法)
__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;
}
};
接下来,我们看一下这段代码:
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
可以看到,__main_block_impl_0
函数传递了两个参数 (void *)__main_block_func_0
、&__main_block_desc_0_DATA
第一个参数 (void *)__main_block_func_0
,这个函数里边封装的就是 block的执行逻辑代码
最终 这个函数指针赋值给了 struct __block_impl
里边的void *FuncPtr
。也就是说 FuncPtr
里边存储的就是将来要执行的 block函数的地址。
接着,我们看 block的执行代码
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
//简化之后
block1->FuncPtr(block1);
可以看到,执行block内部的代码,其实是找到 FuncPtr
指针,然后去执行。
block1之所以可以直接找到 FuncPtr
,是因为 将 block1强制转换成了 __block_impl
这种类型,然后再找到 __block_impl
里边的 FuncPtr
__main_block_impl_0
这种类型的block 之所以 能够转换成为 __block_impl
类型,是因为 __main_block_impl_0
里边的第一个成员就是 __block_impl
类型的,这两个的地址是一样的。
从另一种方面来说,由于__main_block_impl_0
类型的第一个成员的类型是 struct __block_impl
,所以 __main_block_impl_0
也可以直接看成是如下结构:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
//struct __block_impl impl;
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
//构造函数(类似于OC的init方法)
__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为什么可以直接找到 FuncPtr
了。
其实,我们还可以通过以下代码查看 block
void (^block1)(void) = ^{
NSLog(@"This is block!!!");
};
block1();
NSLog(@"%@ %@ %@ %@",[block1 class]
,[[block1 class] superclass]
,[[[block1 class] superclass] superclass]
,[[[[block1 class] superclass] superclass] superclass]);
输出结果:可以看到,block
最终继承自 NSObject
,再次证明 block
其实就是一个OC对象
。
变量的捕获
为了保证 block 内部能够正常访问 外部变量的值, block有个变量捕获机制
在C语言
的函数中可能使用的变量
- 自动变量(局部变量)-- 离开作用域就会自动销毁
- 函数的参数
- 静态局部变量
- 静态全局变量
- 全局变量
其中,静态局部变量
、静态全局变量
、全局变量
,虽然这些变量的作用域不同,但是在整个程序当中,一个变量总保持在一个内存区域。因此,即使在多个函数中使用这些变量,这些变量的值总是保持不变。
那么,哪些变量可以被捕获到block内部呢?先看下边的结论
局部变量会被捕获到block内部,全局变量不会被捕获到block内部。
局部变量的捕获
直接上代码:定义了 两个变量 a 和 b, 运行,查看输出结果
02-block变量捕获[2020:161392] a = 10, b = 20
接着看转换的c++代码。
可以看出,变量a 和 变量b 都被捕获到了 block 内部。并且,变量a 和 变量b 的捕获方式是一模一样的,都是直接把值传递进去(值传递)。
//这两句代码是等效的 默认前边都是有一个 auto 关键字
int a = 10;
auto int a = 10;
接下来,修改代码,如下图:
从运行输出结果可以看出
static
修饰的局部变量 和 auto
修饰的局部变量的结果是不一样的。
重新生成一下xxx.cpp代码,查看一下:
可以看出,
static
修饰的局部变量 传递的是 地址值,被block捕获到内部的是一个指针。(指针传递)
关于局部变量的捕获:
- auto类型:会被捕获, 捕获方式:
值传递
- static类型:会被捕获, 捕获方式:
指针传递
全局变量的捕获
继续修改代码,如下如所示:
全局变量a 和 静态全局变量b 获取到的都是最新的值。
重新生成cpp文件
可以看出,全局变量a 和 静态全局变量b 并没有被捕获到 block内部。
总结: