block初体验
定义和使用block很简单:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^sl_block)(int) = ^(int a1){
NSLog(@"aaaaa:%d",a1);
};
sl_block(12);
NSLog(@"Hello, World!");
}
return 0;
}
但是作为一个有理想的programmer,不仅要知道其怎么用,还要知道其内在原理。
揭开面纱
首先,将定义了上述block的main.m函数用clang 转换为main.cpp:
clang -rewrite-objc main.m
在同目录下会生成一个同名的.cpp文件。
打开cpp文件,找到main函数:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_1);
}
return 0;
}
为了方便理解,先用typedef将其简化:
typedef void(*SL_BLOCK_TYPE)(int);
typedef void(*FuncPtr_TYPE)(__block_impl *, int);
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
SL_BLOCK_TYPE sl_block = SL_BLOCK_TYPE &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
(FuncPtr_TYPE((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_1);
}
return 0;
}
先看第一句:
SL_BLOCK_TYPE sl_block = SL_BLOCK_TYPE &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
找到其中的未知函数或者变量:__main_block_impl_0、__main_block_func_0、__main_block_desc_0_DATA,在文件中找到其定义。
先看__main_block_impl_0:
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;
}
};
可以看出,他是一个结构体,而上述__main_block_impl_0((void *, void *)是调用了其构造函数,返回一个__main_block_impl_0类型的结构体,并对该结构体取地址作为变量sl_block的值。
继续研究__main_block_impl_0结构体的内部定义,发现其包含另外两个结构体:
struct __block_impl impl;
struct __main_block_desc_0* Desc;
先看第一个,找到其具体定义:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
这个好理解,就是简单的结构体,里面包含了两个int型数据和两个指针。为了理解,不在类型之间跳来跳去,直接__block_impl结构体消除,即将__main_block_impl_0结构体定义为:
struct __main_block_impl_0 {
// struct __block_impl impl;
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
//impl.isa = &_NSConcreteStackBlock;
isa = &_NSConcreteStackBlock;
Flags = flags;
FuncPtr = fp;
Desc = desc;
}
};
然后看__main_block_desc_0:
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)};
这里不仅定义了一个结构体,还定义并初始化了一个全局变量__main_block_desc_0_DATA。
因为这个结构体保存的信息为一个保留位reserved和block的size,就不做细研究。
__main_block_impl_0结构体解析完了,回到刚刚的位置,三个位置变量,两个(__main_block_impl_0、__main_block_desc_0_DATA)已经知道是什么了,还剩下一个__main_block_func_0。在文件中找到这个关键字的定义:
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_0,a1);
}
很明显可以看出,这是一个函数,更确切的说,这就是block的实现部分。在sl_block的定义和赋值中可以看出,这个函数被当成参数传递给了及结构体__main_block_impl_0的构造函数,而结构体的构造函数中,又将该函数指针,赋值给了其成员变量FuncPtr。
到这里,基本就很明朗了:
定义一个block,实际上是定义了一个结构体,而block中要执行的代码,则被定义为一个函数,并且该函数的函数指针被结构体的FuncPtr成员所持有。
最后,看看block是怎么被调用的吧:
(FuncPtr_TYPE((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
这个应该很容易就看出来了,就是函数指针的调用。取sl_block结构体中的FuncPtr成员,然后调用。
深入研究----捕捉外部变量
我们都知道,在block中,我们可以使用一些外部变量,那么,这就有一个问题了,block是怎么捕捉外部变量的呢?
将之前的main.m修改一下,再rewrite成cpp,细究。
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
int i_a = 111;
int i_b = 222;
void(^sl_block)(int) = ^(int a1){
NSLog(@"%d==%d",i_a,i_b);
NSLog(@"aaaaa:%d",a1);
};
sl_block(12);
NSLog(@"Hello, World!");
}
return 0;
}
main.cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int i_a = 111;
int i_b = 222;
void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i_a, i_b));
((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_2);
}
return 0;
}
可以看出,__main_block_impl_0构造函数的参数发生了变化,传递了两个参数:i_a,、i_b。
看看其结构体内部变化:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i_a;
int i_b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i_a, int _i_b, int flags=0) : i_a(_i_a), i_b(_i_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体多了两个和外部变量同名同类型的成员变量。
再看其实现部分:
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
int i_a = __cself->i_a; // bound by copy
int i_b = __cself->i_b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_0,i_a,i_b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_1,a1);
}
可以看出,block内部使用的实际上不是外部变量,而是其成员变量,即外部变量的副本(bound by copy)。
综上所述,可以得出,block对于用到的外部变量,会将其保存为成员变量,以供block内部使用。
__block关键字
既然说到block对于外部变量的捕捉,就不得不提__block关键字。
对于上述的例子,外部变量对于block是readonly的。那么怎么才能readwrite呢?apple在设计block的时候肯定是早就考虑到了这个的,__block就是为了解决外部变量的write问题。
ps:在C语言的角度考虑这个问题
其实block的实现,基本都是基于c和c++的,所以考虑这个问题,很自然就想到了c语言的值传递和地址传递。(有兴趣可自行研究)
apple的实现
修改main.m,并rewrite成cpp:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int i_a = 111;
int i_b = 222;
void(^sl_block)(int) = ^(int a1){
NSLog(@"%d==%d",i_a,i_b);
NSLog(@"aaaaa:%d",a1);
i_a= i_b + i_a;
};
sl_block(12);
NSLog(@"Hello, World!");
}
return 0;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_i_a_0 i_a = {(void*)0,(__Block_byref_i_a_0 *)&i_a, 0, sizeof(__Block_byref_i_a_0), 111};
int i_b = 222;
void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i_b, (__Block_byref_i_a_0 *)&i_a, 570425344));
((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d79e13_mi_2);
}
return 0;
}
对比之前的,发现i_a变量,变成了一个结构体__Block_byref_i_a_0:
struct __Block_byref_i_a_0 {
void *__isa;
__Block_byref_i_a_0 *__forwarding;
int __flags;
int __size;
int i_a;
};
而且在传参的时候,也是变成了地址传递((__Block_byref_i_a_0 *)&i_a,)。这就为修改i_a的值奠定了基础。因为地址传参之后修改的肯定不再是副本,而是本身。
再看看实现部分:
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
__Block_byref_i_a_0 *i_a = __cself->i_a; // bound by ref
int i_b = __cself->i_b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d7c9ac_mi_0,(i_a->__forwarding->i_a),i_b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d7c9ac_mi_1,a1);
(i_a->__forwarding->i_a)= i_b + (i_a->__forwarding->i_a);
}
到这里基本就明了了,__block修饰的变量,传递方式变成了地址传递,也就可以达到readwrite的效果了~
最后,(i_a->__forwarding->i_a)= i_b + (i_a->__forwarding->i_a);这个骚操作没搞懂,没有搞错的话,i_a和i_a->__forwarding是同一个地址。
__forwarding
指向自身的操作,是因为block有可能存放在栈上,也有可能存放在堆上,当block没有发生复制时,它指向栈上的对象,当block发生复制时,block指向堆上的对象。