方便记忆:
- Block 实现了对C的闭包实现,一个带有局部变量的匿名函数
- 源码结构分析
- 本体部分:block实际的结构体部分
- 成员变量:impl和Desc
- 结构体的构造函数:__main_block_impl_0
- 捕获外部变量
- 局部变量作为Block结构体的成员变量追加到了__main_block_impl_0
- 根据传递给构造函数的参数对由局部变量追加的成员变量进行初始化
- __main_block_func_0中进行了变量获取_cself->ch(变量的值-深拷贝)
- 改变Block里的值
- c方法:静态变量、静态全局变量、全局变量
- __block修饰符:变成了结构体实例,初始化_main_block_impl_0的时候追加的是_block生成的结构体指针
- 三种Block本体
- NSConcreteStackBlock:在局部定义的(栈)
- NSConcreteGlobalBlock:在全局中定义的(数据区域)-不存在对局部变量的捕获
- NSConcreteMallocBlock:分配在堆中-Block从栈上复制到堆上的方法
Block其实就是C语言的扩充功能,实现了对C的闭包实现,一个带有局部变量的匿名函数。
1.声明一个Block:
使用场景一:
返回值 (^名称) (参数列表) = ^(参数列表) { };
int (^name)(int, int) = ^(int a, int b) { return (a+b); };
使用场景二:
作为一个函数的参数:
- (void)testBlock:(NSString *(返回类型) (^)(int a))s (block名字) { NSString *a = s(1); } { [self testBlock:^NSString *(int a) { a = 5; return @"1"; }]; }
2.Demo 中文件对照
- mainTestBlock.cpp -> 基本的block转译
- mainTestBlockValue.cpp -> block捕获外部变量
- mainTestBlockValueC.cpp -> 通过C语言变量访问值
- mainTestBlockValueValueBlock.cpp -> 通过__block变量访问值
3.从底层分析Block的实现
先从最简单的看起
void (^block)(void) = ^(void) { }; block();
使用 Clang(LLVM)转化为 C++ 的实现
clang -rewrite-objc 文件名
注意:由于 ViewController 中使用 UIKit 库,编译时会出现找不到文件的情况。
转化后的代码如下:
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("aaa\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() { void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; }
源码结构分析
1.block实际的结构体部分(本体)
struct __main_block_impl_0 { struct __block_impl impl; // 结构体 impl,见下 2 struct __main_block_desc_0* Desc; // 结构体 Desc,见下 3 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { // 构造函数,见下 4 impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
2.再看看第一个成员变量impl
struct __block_impl { void *isa; // struct 中,首地址是 *isa,证明 block 是 objc 中的对象 int Flags; // 标记 int Reserved; // 标记 void *FuncPtr; // 函数指针,block所需要执行的代码段 };
他的第一个属性也是一个结构__block_impl,而第一个参数也是一个isa的指针。
在运行时,NSObject和block的isa指针都是指向对象的一个8字节。 NSObject及派生类对象的isa指向Class的prototype,而block的isa指向了_NSConcreteStackBlock这个指针。 就是说当一个block被声明的时候他都是一个_NSConcreteStackBlock类的对象。
3.第二个成员变量Desc
static struct __main_block_desc_0 { size_t reserved; // 版本升级所需的区域 size_t Block_size; // Block的占用空间大小 }
4.第三个就是这个结构体的构造函数(可以理解为对象的初始化方法)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; // block 的本体类型,后面专门讲解 impl.Flags = flags; impl.FuncPtr = fp; // void *fp的指针赋值给了FuncPtr指针 Desc = desc; // desc 结构体的指针传入的初始化 }
以下是源码调用分析
5.现在来看看Main函数中调用的基本转换(初始化转换)
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
看起来这个函数好吓人啊,没事,咱们把类型都去了,而且分开两步来写
/* 调用结构体函数初始化 struct __main_block_impl_0 impBlock = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA); /* 赋值给该结构体类型指针变量 struct __main_block_impl_0 *block = &impBlock;
把栈上生成的__main_block_impl_0的结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block
__main_block_func_0就是转换成的函数指针,这样void (*block)(void) = ^{}这句简单的代码最终就是上面的结构体初始化函数的内部实现逻辑
6.再来细看下初始化函数内部的实现__block_main_impl_0
__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
第一个参数是由Block块语法转换的正真内部函数指针,第二个参数就是作为静态全局变量初始化__main_block_desc_0的结构体实例指针,初始化如下
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
最终,初始化内部进行赋值
isa = &_NSConcreteStackBlock Flags = 0 Reversed = 0 FuncPtr = __main_blcok_func_0 // 就是Block块代码转换成的C语言函数指针 Desc = &__ main_block_desc_0_DATA
7.最终 block() 调用的内部实现
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
老规矩 ,去掉类型就是
(*block->imp.FuncPtr)(block);
打印换行
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("aaa\n");}
没错,最后就是直接调用函数指针进行最终的调用,由上面所描述的,FuncPtr就是由__main_block_func_0的函数指针所赋值的指针,而且可以看出,这个函数的参数正是指向block本身结构体实例,block作为参数进行了传递
综上所述,block的实质,就是一个对象,包含了一个指向函数首地址的指针,和一些与自己相关的成员变量。
4.Block 访问外部变量是怎么回事
int main(){ int a = 100; int b = 200; const char *ch = "b = %d\n"; void (^block)(void) = ^{ }; b = 300; ch = "value had changed.b = %d\n"; block(); return 0; }
Clang 转化后代码
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; const char *ch; int b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) { 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 *ch = __cself->ch; // bound by copy int b = __cself->b; // bound by copy printf(ch,b); } 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 a = 100; int b = 200; const char *ch = "b = %d\n"; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b)); b = 300; ch = "value had changed.b = %d\n"; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; }
1.首先看看Block的内部结构本尊和没有截获的区别
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; const char *ch; int b; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
根据main函数里面的Block语法,这里把Block块表达式里面的局部变量作为了这个Block结构体的成员变量追加到了__main_block_impl_0的结构体中
值得注意的是:
- 结构体内声明的成员变量类型与局部变量类型完全相同
- 语法中没有使用的局部变量(例如咱们这里的a变量)不会被追加
- 细节需要注意,这里截获的ch是不可修改的,而且捕捉的b只是截获了该局部变量的值而已(下面在将如何截获指针)
2.再来看看该结构体实例化的构造函数以及调用和没有截获的区别
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const charchar *_ch, int _b, int flags=0) : ch(_ch), b(_b) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b));
根据传递给构造函数的参数对由局部变量追加的成员变量进行初始化。(这里传递的参数仅仅只是ch和b的值而已)
理解为把Block语法块里面截获的局部变量对__main_block_impl_0的成员追加并且传递赋值,如:
impl.isa = &_NSConcreteStackBlock; impl.Flags = 0; impl.FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA; ch = "b=%d\n"; b = 200;
由此可以看出,__main_block_impl_0的结构体(即Block)对自动变量进行了截获