参考书籍:《Effective Objective-C 2.0》 《Objective-C高级编程 iOS与OS X多线程和内存管理》
根据书中所说,Block是“带有自动变量值的匿名函数”,下面算是我的读书笔记,附上由clang编译出来的过程代码来理解Block的实质。
首先介绍下clang,Clang是一个[C语言]、[Objective-C]、C++语言的轻量级编译器。---百度百科
1.相关clang命令
可能用到的关于clang的指令如下:
cd到main目录下,执行clang -rewrite-objc main.m指令,可以将OC源码转换成C语言源代码,但要注意这里转换出来的C源码并不是最终程序执行的源码,只是过程代码,所以仅便于我们从更加底层的了解OC源码。源码无错且执行完毕后同级目录下便会出现main.cpp文件。
1.若要指定模拟器环境下运行:
首先可执行xcodebuild -showsdks查看本地装有的SDK
然后执行xcrun -sdk iphonesimulatorx.x clang -rewrite-objc main.m(x.x即为本机安装的模拟器版本)
2.若指定真机运行
xcrun -sdk iphoneosx.x clang -rewrite-objc main.m
3.若代码中import了第三方的SDK,可以通过下列命令关闭
xcrun -sdk iphonesimulator10.0 clang -rewrite-objc -F /Users/Desktop main.m
注意,在mac系统下,无需先建工程再执行源码转化,在任意目录先新建一个m文件即可转换。
2.Block源码
先举一个简单的block,方便查看Block的结构。
OC源码:
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^aBlock)() = ^{
NSLog(@"Hello world !");
};
aBlock();
}
return 0;
}
转换后的相关C源码如下:(转换出来的源码虽然有九万多行,但大部分是因为我导入了Foundation框架,以下只列出与main有关的部分源码)
虽然书中已经写的很详细了,但我这里重新理一遍理解的思路,一方面是便于自己梳理要点便于记忆,另一方面有关于书中有些疑问,在这里记下来,方便日后交流与回顾。
下面根据上图中的标号顺序来理解这段转换成C源码的实现原理:
1.先来看看一眼就能认出的block内容部分,在OC中添加的block内容为^{NSLog(@"Hello world !");};
在标注1的地方正好能看到熟悉的NSLog,所以可以看出静态函数__main_block_func_0即对应OC中block中执行的函数部分。
2.接着来看main_block_func_0中传递的参数为 main_block_impl_0结构体类型的 cself指针,这里cself就相当于self(不做拓展介绍,更多的细节请自己查阅更加底层的资料)。然后来具体看__main_block_impl_0结构体,在标注2的地方即为这个结构体的成员变量与构造函数。
这个结构体包含两种变量与一个构造函数:
- __block_impl impl
- __main_block_desc_0* Desc;
- 构造函数__main_block_impl_0
前两种结构体变量稍后讲述,先看一下构造函数main_block_impl_0中所传递的参数为void *fp,struct __main_block_desc_0 *desc, int flags=0
其中fp即为指向block要实现的函数指针,desc为block的相关描述信息,直接赋给Desc变量,最后是带默认值的flags变量。
3.先来看__main_block_impl_0结构体:
- isa:指向实例对象,正好表明Block也跟一般的OC对象类似,拥有isa指针,共有三种block类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
- Flags :按位承载 block 的附加信息;
- Reserved:保留变量;
- FuncPtr: 函数指针,指向 Block 要执行的函数
4.再来看__main_block_desc_0:block 的相关描述信息结构体
- reserved:结构体信息保留字段
- Block_size:结构体大小
- 结构体类型变量: __main_block_desc_0_DATA
5.main函数部分
void (*aBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
这句能猜到就对应着void (^aBlock)() = ^{NSLog(@"Hello world !");};
将其强制转化等操作去掉后,就仅剩下了*aBlock = &__main_block_impl_0;
就可以看出这就是一个简单的指针赋值,将main_block_impl_0结构体赋值给aBlock指针。我的理解为通过构造函数构造出一个main_block_impl_0结构体并赋给了一个指针,这个即为block的c层面上的理解。
6.最后看main中的调用block部分:
OC: aBlock(); C: ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
这句代码可简化为*aBlock->impl.FuncPtr,即通过aBlock变量中的impl的FuncPtr函数指针调用函数。