用clang -rewrite-objc 文件名生成.cpp文件。顺着一个一个结构体往下查找。
1.先看一个简单block调用
int main(){
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
main函数原型
int main()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
根据main函数调用,看下__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结构体,看下__block_impl、__main_block_desc_0两个结构体。
__block_impl原型
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__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_func_0原型
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");}
^{printf("Block\n");};变换后的源代码是__main_block_func_0原型。通过Block使用的匿名函数实际上被当做简单的C语言函数来处理。__main_block_func_0的命名也是通过函数名和block出现的顺序来命名。
__main_block_impl_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)};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一个成员变量__block_impl:从名称可以联想到某些标志、今后版本升级所需的区域以及函数指针。
第二个成员变量__main_block_desc_0:版本升级所需的区域和Block的大小。
__main_block_impl_0的构造函数
_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员,这也是Block是对象的原因。
__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;
}
构造函数的使用
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉转换后
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 * blk = &tmp;
该源码将__main_block_impl_0生产的结构体实例指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
使用该Block的部分:blk();
源代码
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换后
(*blk-impl->impl.Funcptr)(blk);
简单的使用函数指针调用函数。
总结:
1.impl.isa = &_NSConcreteStackBlock;可重点理解,帮助理解为什么block也是OC对象。
2.__main_block_func_0是函数实现的具体代码
3.__main_block_impl_0 *__cself需重点关注,这和block截获变量和__block使用有莫大关系。
4.只有理解了block的实质,才能更好的使用Block。
2.截获局部变量值
先看看下面截获变量源码。
int main(){
int val = 10;
const char * fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;//后面的修改不会产生变化
fmt = "These values were changed.val = %d\n";
blk();
return 0;
}
转换后源代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int 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;
}
};
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);
}
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)};
int main()
{
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 = 2;
fmt = "These values were changed.val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
这与前面转码的源代码稍有差异。__main_block_impl_0中多了两个成员变量。
同时需要注意的是:如果Block没有使用的局部变量,是不会被追加的。
__main_block_impl_0构造函数多了两个参数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)
构造函数使用:用构造函数对追加的成员变量进行初始化。
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
由此可知,在__main_block_impl_0结构体中,局部变量被截获。
__main_block_func_0 实现,即^{printf(fmt,val);}转换后
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);
}
这里也充分利用到了__main_block_impl_0 *__cself,即调用__main_block_impl_0的成员变量。
总结:
截获局部变量意味着:Block所使用的局部变量被保存到Block的结构体中。截获自动变量时,将值传递给结构体的构造函数进行保存。
3.__Block
在Block内是无法直接修改外部变量值,但可以使用外部变量。如下,编译器会报错。
int val = 0;
void (^blk)(void) = ^{
val = 1;
};
解决这两个问题有两种办法。第一种:C语言中有一个变量,允许Block改写值。
1.静态变量
2.静态全局变量
3.全局变量。
int global_val = 1;
static int static_global_val = 2;
int main()
{
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *=2;
static_val *= 3;
}
}
转换后
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
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_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *=2;
(*static_val) *= 3;
}
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)};
int main()
{
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
}
对静态全局变量static_global_val和全局变量global_val的访问与访问前完全相同。对于静态变量static_val稍有不同。看看static_val的使用:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
(*static_val) *= 3;
}
使用静态变量static_val的指针对其进行访问。将静态变量的指针传递给__main_block_impl_0构造函数并保持。这是超出作用于使用变量的最简单方法。
第二种方法使用“__Block说明符”。
int main(){
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
转换后
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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(){
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
这个__Block变量val是怎样转换过来的呢?
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
__Block变量也生成了__Block_byref_val_0结构体类型的自动变量。__Block_byref_val_0在__main_block_impl_0中,这意味着该结构体持有相当于原自动变量的成员变量。
__Block_byref_val_0结构体中最后的成员变量val是相当于原自动变量的成员变量。
__main_block_impl_0转换后成员变量多了__Block_byref_val_0:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
^{val = 1;};转换后:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
Block的__main_block_impl_0结构体实例持有指向__Block变量和__Block_byref_val_0结构体实例指针。__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。
__forwarding在栈上和拷贝到堆上有什么区别呢?后面会讨论这个问题。
另外__Block_byref_val_0为什么要单独声明呢?这样做是为了在多个Block中使用__block变量。
int main(){
__block int val = 10;
void (^blk0)(void) = ^{
val = 0;
};
void (^blk1)(void) = ^{
val = 1;
};
return 0;
}
blk0和blk1访问__block变量val。把访问的部分摘出来:
int main(){
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
去掉强转后:
int main(){
__Block_byref_val_0 val = {0,&val, 0, sizeof(__Block_byref_val_0), 10};
blk0 = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 570425344));
blk1 = &__main_block_impl_1(__main_block_func_1, &__main_block_desc_1_DATA,&val, 570425344));
return 0;
}
两个Block都是使用了__Block_byref_val_0结构体实例val的指针。这样一来就可以从多个Block中使用同一个__block变量。
同样一个Block中使用多个__block变量,只要增加Block的结构体成员变量与构造函数的参数。
总结:
- __block生成时多了__forwarding指针
- __forwarding指针指向结构体自身,用于访问val成员变量。当然,这仅是栈上的指针。