系列文章:
iOS Block概念、语法及基本使用
iOS Block __block说明符
iOS Block存储域及循环引用
本章会讲解以下几点:
- Block实现原理
- Block也是OC对象
- 截获自动变量值
Block实现原理
要想知道Block的内部实现,需要知道Block编译完后是什么样子,使用clang可看到Block编译完后的代码实现。
Block的语法看着特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,将含有Block语法的源代码转换为C语言的源代码,并作为极为普通的C语言源代码被编译。
clang -rewrite-objc 文件名
上述命令使用在命令行项目是没问题的,但是使用在single APP项目中会报错,UIKit/UIKit.h’ file not found
在single APP项目中使用以下命令:
模拟器 xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m
真机 xcrun -sdk iphoneos clang -rewrite-objc xxx.m
使用命令后会生成一个 xxx.cpp的文件。
来具体看下Block编译后的代码,先来看命令行项目:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
}
return 0;
}
上述代码是一个很简单的无返回值无参数的Block,下面是使用clang编译后的源代码,只把有用的部分贴出:
struct __block_impl {
void *isa;//block 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;//block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//block表达式转换
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//block调用
}
return 0;
}
如上变换后的源代码,通过Block使用的匿名函数实际上被作为简单的C语言函数来处理,另外Block语法所属的函数名(此处为main,后边会贴上 函数名为 viewDidLoad)和该Block语法在该函数出现的顺序值来给经clang变换的函数命名。
下面具体一步一步来看看上述代码:
__main_block_impl_0结构体的声明:第一个成员变量是impl,第二个成员变量是Desc。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
__block_impl 结构体的声明:
struct __block_impl {
void *isa;//block isa指针
int Flags;
int Reserved;
void *FuncPtr;//函数指针
};
__main_block_desc_0 结构体声明:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;//block大小
}
__main_block_impl_0实例的构造函数:
__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表达式生成部分:初始化一个__main_block_impl_0结构体实例并赋值给blk变量。
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
__main_block_func_0结构体声明,__main_block_func_0该函数的参数__cself是 __main_block_impl_0 结构体实例,该实例其实就是转换前的Block实例。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
去掉转换部分:
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &temp;
上边代码对应的源代码就是以下代码:
void (^blk)(void) = ^{
printf("Block\n");
};
再来看看Block调用部分:blk()
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换部分:
(*blk->impl.FuncPtr)(blk);
正如刚才看到的编译后的代码,block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中,另外也说明了__main_block_func_0函数的参数__cself指向Block值。在调用该代码的源代码中可以看出Block正是作为参数进行了传递。
Block也是OC对象
impl.isa = &_NSConcreteStackBlock;
只要你理解OC对象内的isa指针,你就能理解这里的isa指针。block的isa指针指向自己所属的Block类。
OC对象都有一个isa指针,指向属于当前的类。以下是
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
以下贴出single APP 使用命令编译后的源代码实现,使用命令:
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
源代码:
- (void)viewDidLoad {
[super viewDidLoad];
int (^block)(int,int) = ^ int (int a,int b) {
return a + b;
};
int sum = block(1,2);
NSLog(@"%d",sum);
}
编译后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int a, int b) {
return a + b;
}
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
int (*block)(int,int) = ((int (*)(int, int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
int sum = ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9k_z85dfkt91zd1j387gcxn8xkh0000gn_T_ViewController_b0d5a8_mi_0,sum);
}
截获自动变量值(瞬间值)
编译以下源代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;
fmt = "These value were changed. val = %d\n";
blk();
}
return 0;
}
编译后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
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);
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;//传入的变量 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;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
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));//结构体带着参数val初始化并赋值
val = 2;
fmt = "These value were changed. val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//函数指针调用
}
return 0;
}
这样来看就能想明白为什么val、fmt变量改变,而block表达式内部的变量值不改变了。即Block截获的自动变量值是变量的瞬间值,在编译完成后就已经确定了,即便改变Block内部用到的变量的值,Block调用的时候,表达式内部的变量值也不会改变。