为什么外部变量加上__blcok之后就可以在block内部进行修改。
通过clang把OC重写成C++来看一下__block究竟做了什么。
clang -rewrite-objc main.m或者
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m
1.先看一下没有使用__block的非对象变量:
int main () {
int num = 1;
void (^aBlock) (void) = ^{
printf(" 输出 num == %d", num);
};
aBlock();
return 0;
}
重写完后的代码很多,挑主要的部分展示一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
printf(" 输出 num == %d", num);
}
int main () {
int num = 1;
void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
return 0;
}
先看main函数里
void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
aBlock在这列是一个指针变量,指向了一个__main_block_impl_0类型的结构体,这一段拆开来可以看作是这样的:
struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
struct __main_block_impl_0 *aBlock = &tmp;
来看一下结构体__main_block_impl_0的定义:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在block里捕获到的外部变量num会作为结构体的成员变量,并且会在构造方法里对成员变量num赋值,构造方法里可以看作是这样的:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
num = 0
我们之前在block写的代码在这里是一个静态函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
printf(" 输出 num == %d", num);
}
构造器的第一个参数就是这个函数的地址:
struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
这个函数的地址会存储到结构体成员变量里:
impl.FuncPtr = fp;
之后调用aBlock()对应的是:
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
这个可以看做是:
(*aBlock->impl.Funcptr)(aBlock);
调用blcok也就是通过函数指针Funcptr调用了函数,并且把aBlock指向的结构体作为函数参数。
刚刚那个函数里:
int num = __cself->num; // bound by copy
block里面使用的num是一个新定义的num,它是值拷贝,我们无法通过它修改外部的变量。在OC里面如果修改的话编译器也会直接报错。
接下来看一下加上__block之后的效果:
int main () {
__block int num = 1;
printf("输出前 num == %p", &num);
void (^aBlock) (void) = ^{
num ++;
printf("输出中 num == %p", &num);
};
aBlock();
printf("输出后 num == %p", &num);
return 0;
}
先看重写之后的main函数
int main () {
printf("输出前 num == %p", &(num.__forwarding->num));
void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
printf("输出后 num == %p", &(num.__forwarding->num));
return 0;
}
__block int num 被重写成了
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 1};
num在这里是一个结构体。
block结构体是这样的:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
blcok结构体会持有__block结构体的指针,函数里面修改num是这样的:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
(num->__forwarding->num) ++;
printf("输出中 num == %p", &(num->__forwarding->num));
}
函数里是先获取到了__block结构体的指针,然后修改了__block的成员变量num。
首先可以看到,加上__block之后的外部变量,重写之后main函数里第一行不再是int num = 1
,而是定义了一个 __Block_byref_num_0 num = ...
结构体,并且初始化时这个结构体的forwad是指向它自己的,我们定义的int 值存在了结构体的成员变量num里面。
再看一下这里的__main_block_func_0
这个函数里,它里面不再像没有加__block那样新定义一个int num
,而是新定义了一个指针__Block_byref_num_0 *num
,在OC里面的a++在这里被重写成了(num->__forwarding->num) ++
;也就是说它是通过指针实现了修改外部变量。
接下来在MRC和ARC环境下分别运行下面的代码:
__block int num = 1;
printf("输出前 num == %p\n", &num);
void (^aBlock) (void) = ^{
num ++;
printf("输出中 num == %p\n", &num);
};
aBlock();
printf("输出后 num == %p\n", &num);
MRC下打印出来的三个地址是一样的:
输出前 num == 0x7fff547606c8
输出中 num == 0x7fff547606c8
输出后 num == 0x7fff547606c8
而ARC下打印的地址后两个是一样的:
输出前 num == 0x7fff547606c8
输出中 num == 0x604000431018
输出后 num == 0x604000431018
MRC下上面的aBlock和num都是在栈上的,结构体的__forwarding指针始终指向它自己,所以三个地址打印出来的是一样的。
在ARC下,上面的aBlock是在堆上的,而num也会从栈上copy到对上,而原来在栈上的结构体__forwarding指针会指向在堆上的结构体,这时打印的地址是在堆上的num的地址。
2.再来看一下外部对象变量:
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%@, %p", obj, &obj);
void (^aBlock)(void) = ^{
NSLog(@"%@, %p", obj, &obj);
};
aBlock();
NSLog(@"%@, %p", obj, &obj);
重写之后的代码block里面的实现:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_main_4c9416_mi_1, obj, &obj);
}
int main(int argc, char * argv[]) {
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block3_e8baad_mi_0, obj, &obj);
void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block3_e8baad_mi_2, obj, &obj);
return 0;
}
可以看出来,block里的obj对象是新定义的一个指针,因此不能够通过修改它来修改外部的obj,所以在block里不能够修改obj的值。
再看一下加上__block之后的代码:
__block NSObject *obj = [[NSObject alloc] init];
void (^aBlock)(void) = ^{
obj = [[NSObject alloc] init];
NSLog(@"%@, %p", obj, &obj);
};
aBlock();
重写之后的代码:
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref (obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_1, (obj->__forwarding->obj), &(obj->__forwarding->obj));
}
int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_0, (obj.__forwarding->obj), &(obj.__forwarding->obj));
void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_2, (obj.__forwarding->obj), &(obj.__forwarding->obj));
return 0;
}
从上面可以看出,加上__block之后,对象obj会被编译成一个结构体__Block_byref_obj_0 obj
,并且在block的实现里面__Block_byref_obj_0 *obj = __cself->obj
使用一个新的结构体指针引,通过这个结构体指针就可以访问到结构体里存储的obj对象并进行修改。