例子1
main.m文件的代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 1;
void (^myBlock)(int, int) = ^(int a, int b) {
NSLog(@"a = %d, b = %d", a, b);
};
myBlock(2, 3);
}
return 0;
}
执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 后,打开生成的main-arm64.cpp文件,会看到如下源码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //block代码块的内容被封装成了一个函数(函数名为__main_block_func_0),该函数的地址就是FuncPtr变量的值。
};
struct __main_block_impl_0 { //block被封装成了该结构体
struct __block_impl impl; //block的描述信息
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, int a, int b) { //包含了block代码块的内容
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_faed76_mi_0, a, b); //打印a和b
}
static struct __main_block_desc_0 { //block的描述信息
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;
int age = 1;
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //myBlock指向一个__main_block_impl_0结构体实例
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2, 3); //调用__main_block_func_0()函数
}
return 0;
}
例子2
在main函数里面的block中添加age的打印信息,此时main.m文件的代码如下:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 1;
void (^myBlock)(int, int) = ^(int a, int b) {
NSLog(@"age = %d", age); //新增代码
NSLog(@"a = %d, b = %d", a, b);
};
myBlock(2, 3);
}
return 0;
}
执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 后,打开生成的main-arm64.cpp文件,会看到如下源码:
struct __main_block_impl_0 { //block被封装成该结构体
struct __block_impl impl;
struct __main_block_desc_0* Desc; //block的描述信息
int age; //因为block里面使用了main函数的age局部变量,所以main.m被转化成cpp代码时,age被封装成了__main_block_impl_0结构体的成员变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_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 a, int b) { //block里面的内容都被封装到该函数中
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_432cf3_mi_0, age); //打印age
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_rl1qfshn6n9dl6p87b_pjdtr0000gp_T_main_432cf3_mi_1, a, b); //打印a和b
}
static struct __main_block_desc_0 { //block的描述信息
size_t reserved;
size_t Block_size; //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //保存着你定义的block的大小
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 1;
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); //定义myBlock变量并初始化
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2, 3); //函数调用
}
return 0;
}
运行结果如下
从上面的两个例子可以看出:block本质上也是一个oc对象,它内部也有一个isa指针。block里面的内容被封装成了一个函数。
思考题1:block内部使用block外面的局部变量(也称auto变量)
为什么下图的打印结果是1
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:
思考题2:block内部使用block外面的静态局部变量(也称static变量)
为什么下图的打印结果是weight = 4
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:
age是局部变量,block对应的__main_block_impl_0结构体中就定义了一个age成员变量,该变量存的是局部变量age的值。而weight是静态局部变量,block对应的__main_block_impl_0结构体中就定义了一个指向weight的成员变量,该变量存的是静态局部变量weight的地址。
思考题3:block内部使用block外面的全局变量和文件内的全局变量(也称global变量)
为什么下图的打印结果是age = 2,weight = 4 ?
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m ,进而将main.m文件转为main.cpp文件,然后打开main.cpp文件,找到main函数所在位置,然后分析打印结果:block对应的结构体__main_block_impl_0
不会生成全局变量的副本,在block内部对全局变量直接使用即可。
思考题3:在一个类的方法内部的block使用self
demo的代码如下
打印结果如下
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m ,进而将Person.m文件转为Person.cpp文件,然后打开Person.cpp文件,找到myBlock所在位置。可以看到Person类的test方法在转化成c++后,会变成_I_Person_test()函数,且该函数的第1、2个参数分别是 self 和 _cmd ,既然是函数参数,所以self 和 _cmd 都是局部变量,既然是局部变量,那么block在使用该局部变量时,就会在block对应的结构体里面添加该成员变量。每一个类的方法(比如实例方法或者类方法,即方法开头是 - 号 或者 + 号的方法),默认都会有两个参数:self 和 _cmd 参数。
思考题4:在一个类的方法内部的block使用类的成员变量
demo的代码如下
打印结果如下:
答案:第一步,执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person.m ,进而将Person.m文件转为Person.cpp文件,然后打开Person.cpp文件,找到myBlock所在位置。可以看到Person类的test方法在转化成c++后,会变成_I_Person_test()函数,且该函数的第1、2个参数分别是 self 和 _cmd ,既然是函数参数,所以self 和 _cmd 都是局部变量。但是block对应的__Person__test_block_impl_0
结构体里面只是添加了self成员变量,并不会添加Person类的name成员变量。在调用block时,block对应的__Person__test_block_func_0
函数会通过Person类类型的self找到Person的成员变量name,然后打印。
思考题5:block本质上也是一个oc对象
思考题5:block对应的类的类型
block对应的类的类型有3种:
- NSGlobalBlock :存储在全局区,当block内部没有使用局部变量(auto变量)时,该block的类型就是 NSGlobalBlock 类型。
- NSStackBlock :存储在栈上,当block内部使用了局部变量(auto变量)时,该block的类型就是 NSStackBlock 类型。当定义该block的函数弹栈时,该block就会被销毁。
- NSMallocBlock :存储在对上,当类型为__NSStackBlock__ 的block调用copy方法时,即 [stackBlock copy]的返回值就是__NSMallocBlock__ 类型的block。在ARC的编译环境下,类型为__NSStackBlock__ 的block会在编译时自动调用调用[block copy],进而返回__NSMallocBlock__ 类型的block。
- 下图是打开ARC环境的编译运行结果。
- 下图是关闭ARC环境的编译运行结果,上图和下图的差别就是block2的类型。在关闭了ARC环境时,即此时为MRC环境,对于block2的赋值操作,系统会执行:
void (^block2)(void) = ^ { NSLog(@"autoVar = %d", autoVar); };
,从而把 NSStackBlock 类型的block赋值给block2。但在开启ARC环境时,对于block2的赋值操作,系统会执行:void (^block2)(void) = [^ { NSLog(@"autoVar = %d", autoVar); } copy];
,从而把__NSMallocBlock__类型的block赋值给block2。
思考题5:类型为__NSStackBlock__ 的block的潜在问题
- 下图是在关闭ARC的环境下,即为MRC环境下的运行结果。类型为__NSStackBlock__ 的block会出现野指针错误。
- 下图是在打开ARC的环境下,即为MRC环境下的运行结果。类型为__NSMallocBlock__的myBlock是分配在堆上的,所以test()函数执行完后,myBlock执行的内存也不会被回收,所以此时age的值是能正确访问的(因为age作为myBlock指向的结构体对象的所属类的一个成员变量)。
age作为myBlock指向的结构体对象的所属类的一个成员变量
思考题5:block内部使用block外部的对象类型的auto变量所指向的对象的销毁问题(ARC和MRC环境下是不同的)
demo的目录结构:
main.m文件的代码如下:
Person.h文件:
Person.m文件
- block内部使用强引用指向person对象的运行结果:
- block内部使用弱引用指向person对象的运行结果:
执行命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
,查看 “block内部使用弱引用指向person对象”的c++实现,发现block对应的__main_block_impl_0
结构体里面的weakPerson成员变量是弱引用类型。。。
__block的原理
demo及其运行结果如下图
执行命令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
命令,可以得到对应的main.cpp文件,该cpp文件的关键代码如下: