探索OC对象的实现原理的最好方式就是查看它的C++代码实现,下面使用该方式探索一下Block的实现原理。
Block捕获自动变量
OC源码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
int lucy = 128;
int jack = 256; //没有在block中使用
NSString *name = @"david";
void (^blk)(void) = ^{
NSLog(@"%d == %@",lucy,name);
};
blk();
return 0;
}
进入到.m文件目录,clang -rewrite-objc main.m,会在当前目录中生成同名 .cpp 文件,打开 main.cpp 文件;
C++
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结构体的一个描述信息
int lucy; //捕获了外部的自动变量
NSString *name; //捕获了外部的自动变量
//block结构体构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _lucy, NSString *_name, int flags=0) : lucy(_lucy), name(_name) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int lucy = __cself->lucy; // bound by copy(值传递)
NSString *name = __cself->name; // bound by copy(值传递)
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_1,lucy,name);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved; //今后升级版本所需区域
size_t Block_size; //block的大小
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
int lucy = 128;
int jack = 256;
NSString *name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_0;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, lucy, name, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
解释:
-
在block内部语法表达式中使用的自动变量(lucy,name)被作为成员变量追加到了
__main_block_impl_0结构体中; -
变量 jack 没有在block中使用,所以没有出现在
__main_block_impl_0结构体中; -
在初始化block结构体实例时(
请看__main_block_impl_0的构造函数),还需要截获的自动变量lucy和name来初始化__main_block_impl_0结构体实例,因为增加了被截获的自动变量,block的体积会变大。
再来看一下block函数体的C++代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int lucy = __cself->lucy; // bound by copy(值传递)
NSString *name = __cself->name; // bound by copy(值传递)
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_1,lucy,name);
}
从这里看就更明显了:lucy,name 都是从__cself里面获取的,更说明了二者是属于block的。而且从注释来看(注释是由clang自动生成的),这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。
- 在block外部改变自动变量的值
int main(int argc, const char * argv[]) {
int lucy = 128;
int jack = 256;
NSString *name = @"david";
void (^blk)(void) = ^{
NSLog(@"%d == %@",lucy,name);
};
blk();
lucy = 365;
blk(); //输出不变
return 0;
}
2020-11-13 12:31:46.649709+0800 BlockTest[46805:5753123] 128 == david
2020-11-13 12:31:46.650089+0800 BlockTest[46805:5753123] 128 == david
Program ended with exit code: 0
在block外部修改变量的值,不会影响block内部的输出;
- 在block内部改变自动变量的值
int main(int argc, const char * argv[]) {
int lucy = 128;
int jack = 256;
NSString *name = @"david";
void (^blk)(void) = ^{
NSLog(@"%d == %@",lucy,name);
lucy = 333; //编译不通过
};
blk();
return 0;
}
直接在block内改变lucy的值是无法编译的,错误提示 Variable is not assignable (missing __block type specifier)没有指定__block;
这是因为Block内部和外部的变量实际上是两种不同的存在:前者是Block内部结构体的一个成员变量,后者是在栈区里的临时变量。
由此得到结论:
- 在block外部修改变量的值,不会影响block内部
- 在block内无法改变外部自动变量的值
被截获的自动变量的值是无法直接修改的,但是有两个方法可以解决这个问题:
1.改变变量的存储区域
2.给变量添加__block修饰符
1. 改变变量的存储区域
oc源码:
int global_lucy = 128; //全局变量
static int static_global_jack = 256; //全局静态变量
static NSString *static_global_name = @"david";
int main(int argc, const char * argv[]) {
static int static_mark = 1024; //局部静态变量
void (^blk)(void) = ^{
global_lucy += 1;
static_global_jack += 1;
static_global_name = @"david father";
static_mark += 1;
};
NSLog(@"%d = %d = %@ = %d",global_lucy,static_global_jack,static_global_name,static_mark);
blk();
NSLog(@"%d = %d = %@ = %d",global_lucy,static_global_jack,static_global_name,static_mark);
return 0;
}
输出:
2020-11-13 12:47:27.417016+0800 BlockTest[47073:5760762] 128 = 256 = david = 1024
2020-11-13 12:47:27.417428+0800 BlockTest[47073:5760762] 129 = 257 = david father = 1025
看一下对应的c++源码:
int global_lucy = 128; //全局变量
static int static_global_jack = 256; //全局静态变量
static NSString *static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_0;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_mark; //捕获到 局部静态变量 ,这里是指针,不是值
//block结构体构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_mark, int flags=0) : static_mark(_static_mark) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_mark = __cself->static_mark; // bound by copy
global_lucy += 1;
static_global_jack += 1;
static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_1;
(*static_mark) += 1;
}
可以发现:
- 全局变量和全局静态变量没有被截获到block里面,它们的访问是不经过block的,请看
__main_block_func_0函数体,没有通过__cself引用;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_mark = __cself->static_mark; // bound by copy
global_lucy += 1;
static_global_jack += 1;
static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_1;
(*static_mark) += 1;
}
- 访问局部静态变量(static_mark)时,是将局部静态变量的指针传递给__main_block_impl_0 结构体的构造函数,此时不是值传递,是指针传递;
int *static_mark; //局部静态变量(静态变量的指针)
(*static_mark) += 1; //指针所指向的变量 +1 操作
2. 给自动变量添加__block修饰符
oc源码:
int main(int argc, const char * argv[]) {
__block int mark = 1024;
void (^blk)(void) = ^{
mark += 1;
};
blk();
NSLog(@"%d",mark);//输出 1025
return 0;
}
可以看到是预期结果 1025,下面看下对应的c++源码: c++:
struct __Block_byref_mark_0 {
void *__isa;
__Block_byref_mark_0 *__forwarding;
int __flags;
int __size;
int mark;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_mark_0 *mark; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_mark_0 *_mark, int flags=0) : mark(_mark->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_mark_0 *mark = __cself->mark; // bound by ref
(mark->__forwarding->mark) += 1;
}
/*从栈复制到堆时调用*/
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->mark, (void*)src->mark, 8/*BLOCK_FIELD_IS_BYREF*/);
}
/*堆上的Block被废弃时调用*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->mark, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_mark_0 mark = {(void*)0,(__Block_byref_mark_0 *)&mark, 0, sizeof(__Block_byref_mark_0), 1024};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_mark_0 *)&mark, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a9066b_mi_0,(mark.__forwarding->mark));
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
注意,被__block修饰的变量会被转换为如下结构体:
struct __Block_byref_mark_0 {
void *__isa;
__Block_byref_mark_0 *__forwarding;
int __flags;
int __size;
int mark;
};
解释一下 __Block_byref_mark_0 的结构:
- __isa指针,
- __forwarding指针,通过__forwarding,可以实现无论__block变量配置在栈上还是堆上都能正确地访问__block变量,也就是说__forwarding是指向自身的。
- __flags,是标志性参数,暂时没用到所以默认为0。
- __size,是该结构体所占用的大小。
- 保存了最初的mark变量,也就是说原来单纯的int类型的mark变量被__block修饰后生成了一个结构体。这个结构体其中一个成员变量持有原来的mark变量。
结构体类型命名是__Block_byref_ + 变量名 + _ + index(变量出现的顺序)
注意:在__main_block_impl_o结构中并不是存储的__Block_byref_mark_0变量,而是存储的变量的指针地址,这样做是为了能够在多个block中访问到同一个__block变量;
需要特别注意的是__main_block_func_0函数的实现:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_mark_0 *mark = __cself->mark; // bound by ref
(mark->__forwarding->mark) += 1;
}
这里首先依然是通过函数传入的__cself,取到内部的mark实例变量,然后使用mark->__forwarding->mark的形式赋值,为什么要这么做?
借用别人做的一个图:
- 最初__block变量在栈上时,它的成员变量__forwarding指向栈上的__block变量结构体实例。
- 当__block变量被复制到堆上时,会将__forwarding的值替换为堆上的__block变量结构体实例的地址,而在堆上的__block变量自己的__forwarding的值就指向它自己。
所以,mark->__forwarding->mark这样赋值,可以保证不管变量是否从栈区拷贝到了堆区,都能正确的访问实际的变量。
Block捕获对象
1. block中捕获NSArray对象
- 往可变集合里面添加内容
oc代码:
int main(int argc, const char * argv[]) {
id array = [NSMutableArray new];
void (^blkArr)(id) = ^(id object){
[array addObject:object];
NSLog(@"add > %ld",[object integerValue]);
};
blkArr(@"1");
blkArr(@"2");
NSLog(@"%@",array);
return 0;
}
2020-11-13 19:19:45.942159+0800 BlockTest[14932:6098575] add > 1
2020-11-13 19:19:45.942595+0800 BlockTest[14932:6098575] add > 2
2020-11-13 19:19:45.942737+0800 BlockTest[14932:6098575] (
1,
2
)
发现在没有__block修饰的情况下,可以正常给数组对象添加值;
c++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array; //捕获了外部的array变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {
id array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)object);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_c2c16e_mi_0,((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("integerValue")));
}
在__main_block_impl_0结构体中可以看到截获的array;
再看 __main_block_func_0的函数实现,array 是从__cself里面获取的,说明array是属于block的;
- 在block里面给外部集合对象赋值新的可变集合对象
int main(int argc, const char * argv[]) {
id array = [NSMutableArray new];
void (^blkArr)(id) = ^(id object){
[array addObject:object];
id array2 = [NSMutableArray arrayWithObjects:@"2", nil];
array = array2; //编译错误
NSLog(@"add > %ld",[object integerValue]);
};
blkArr(@"1");
NSLog(@"%@",array);
return 0;
}
同样,也会编译不通过;上面已经论述过,添加__block修饰就可以了;
2. 给对象添加__block修饰符
oc代码:
int main(int argc, const char * argv[]) {
__block id array = [NSMutableArray new];
void (^blkArr)(id) = ^(id object){
[array addObject:object];
id array2 = [NSMutableArray new];
[array2 addObject:@"2"];
array = array2;
NSLog(@"add >> %ld",[object integerValue]);
};
blkArr(@"1");
NSLog(@"%@",array);
return 0;
}
2020-11-13 19:30:26.536419+0800 BlockTest[15136:6104254] add >> 1
2020-11-13 19:30:26.536860+0800 BlockTest[15136:6104254] (
2
)
成功显示了array2中的内容;
c++代码:
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
id array;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_array_0 *array; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {
__Block_byref_array_0 *array = __cself->array; // bound by ref
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)(array->__forwarding->array), sel_registerName("addObject:"), (id)object);
id array2 = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("new"));
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_0);
(array->__forwarding->array) = array2;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_1,((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("integerValue")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("new"))};
void (*blkArr)(id) = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344));
((void (*)(__block_impl *, id))((__block_impl *)blkArr)->FuncPtr)((__block_impl *)blkArr, (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_3,(array.__forwarding->array));
return 0;
}
c++代码实现的结构和自动变量添加__block修饰类似;同样会生成__block变量的结构体:__Block_byref_array_0;
可以看到array被添加到了__main_block_impl_0结构体中,它是__Block_byref_array_0类型的;
顺便看下__main_block_func_0函数实现,也使用了(array->__forwarding->array) = array2;的方式进行赋值操作;
总结
- 被__block 修饰的 自动变量、非集合、集合对象都可以赋值。
- 没有__block修饰的集合可以修改其中的对象,但不能赋值,赋值会编译报错。
- 使用__forwarding方式赋值可以保证不管变量是否从栈区拷贝到了堆区,都能正确的访问实际的变量。
- __Block_byref_xx_0结构体不在__main_block_impl_0结构体中,目的是为了使得在多个Block中使用该__block变量。