源:
int main() { void (^blk)(void) = ^{printf();}; blk(); return 0; }
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_ipml_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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf(); } static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; int main() { void (*blk)(void) = (void (*)(void))&__main_block_impl_0( (void *)__main_block_func_0, &__main_block_desc_0_DATA); ((void (*)(struct __block_impl *))( (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); return 0; }
可以通过转换后的源代码看到,通过blocks使用的匿名函数,实际上被作为简单的c语言函数来处理。
在转换后的代码的命名上,是根据block语法所属的函数名和该block语法在该函数出现的顺序值。
来看block的语法:
^{printf();};
对应的是:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf();
}
这里的__cself相当于c++中的this。
函数参数声明:struct __main_block_impl_0 *__cself
与c++的this和oc的self相同,参数__cself 是 __main_block_impl_0结构体的指针。
struct __main_block_ipml_0 { struct __block_impl impl; struct __main_block_desc_0 *Desc; }
这里是取出构造函数的代码,
其中impl是__block_impl结构体。
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
从名称中可以看到这些标志,为今后升级所需的区域还有函数指针。
Desc是指针,是__main_block_desc_0结构体的。
static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; };
这里也是为今后升级所需区域和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; }
可以看到这里有一个_NSConcreteStackBlock函数,他是用户初始化__block_impl结构体的isa成员的。
main中构造函数是如何调用的:
void (*blk)(void) =
(void (*)(void))&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA);
拆分:
struct __main_block_impl_0 tmp = __main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
这样就容易理解了。 该源代码在栈上生成__main_block_impl_0结构体实例的指针。我们将栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blk;
注意到生成tmp的函数的参数, 第一个参数是由block语法转换的c语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针。
看一下结构体初始化:
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
这里使用__main_block_impl_0结构体实例的大小进行初始化。
来看一下栈上的__main_block_impl_0结构体实例(即Block)是图和根据这些参数进行初始化的:
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
该结构体根据构造函数:
isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
接下来看blk();
((void (*)(struct __block_impl *))( (struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk); 我们去掉转换部分: (*blk->impl.FuncPtr)(blk);
这是简单的使用函数指针调用函数。
由Block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中, __main_block_func_0函数的参数__cself指向Block值, 在调用该函数的源代码中可以看出Block正是作为参数进行了传递。
这里的isa = &_NSConcreteStackBlock;
将Block指针赋给Block的结构体成员变量isa。 在这里,我们需要理解oc类和对象的实质, 其实,所谓的block就是oc对象。
id 这个类型是用于存储oc对象的,在源码中,虽然可以像使用void*那样使用id,但是id类型也能够在c中声明。
来看id:
struct objc_class { Class isa; }; typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
id是objc_object结构体的指针类型。
我们来声明一个oc类:
@interface MyObject :NSObject { int val0; int val1; } @end
这个类的对象结构体如下:
struct MyObject { Class class; int val0; int val1; };
MyObject类的实例变量val0,val1被直接声明为对象的结构体成员。
在oc中,由类生成对象,意味着 像该结构体这样,生成由该类生成的对象的结构体实例。生成的各个对象,由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。
如图:
各类的结构体就是基于objc_class结构体的class_t结构体,class_t结构体:
struct class_t { struct class_t *isa; struct class_t *superclass; Cache cache; IMP *vtable; uintptr_t data_NEVER_USE; };
在oc中,如NSObject的class_t结构体实例以及NSMutablearray的class_t结构体实例等,均生成并保持各个类的class_t结构体实例。 该实例特有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针,并被oc运行时库所使用。
看刚刚的结构体:
struct __main_block_impl_0 { void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; };
他就相当于objc_object结构体的oc类对象的结构体。
isa = &NSConcreteStackBlock;
即_NSConcreteStackBlock相当于class_t结构体实例, 在将Block作为oc的对象处理时,关于该类的信息放置在:_NSConcreteStackBlock中。
最后Block即为OC对象。
截获自动变量值
通过clang进行转换的源代码:
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; int val = __cself->val; printf(fmt, val); } static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; int main() { int dmy = 256; int val = 10; const char *fmt = "val = %d\n"; void (*blk)(void) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, fmt, val); return 0; }
首先我们看到:Block语法表达式中,使用的自动变量被作为成员变量追加到了__main_block_impl_0中: int val。
如果Block语法表达式没有使用的自动变量是不会追加的。
Blocks的自动变量截获只针对Block中使用的自动变量。
__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) = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
这里使用执行Block语法时的自动变量fmt和val来初始化__main_block_impl_0结构体实例。
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main)block_desc_0_DATA;
fmt = "val = %d\n";
val = 10;
这样,我们知道 在__main_block_impl_0结构体实例中,自动变量值被截获。
现在这里是使用block的匿名函数的实现:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char &fmt = __cself->fmt; int val = __cself->val; printf(fmt, val); }
这里可以看到,截获到__main_block_impl_0结构体实例的成员变量上的自动变量。
这些变量在Block语法表达式之前被声明定义。 因此,原来的源代码表达式无需改动便可使用截获的自动变量值执行。
自动变量就是 在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例中。
最后,Block不能直接使用c语言数组类型。看一下数组传递的源代码:
void func(char a[10]) {
---;
}
int main() {
char a[10] = {1};
func(a);
}
这里 在构造函数中,将参数赋值给成员变量中,这样在变换了block语法的函数内 可由成员变量赋值给自动变量:
void func(char a[10]) {
char b[10] = a;
---;
}
int main() {
char a[10] = {1};
func(a);
}
这样是不能通过编译的。
__block说明符
在刚刚的代码:^{printf(fmt, val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { const char &fmt = __cself->fmt; int val = __cself->val; printf(fmt, val); }
中,Block中所使用的被截获自动变量就如:带有自动变量值的匿名函数。 所说的,仅截获自动变量的值。
Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
如果在Block中视图改变自动变量,将引起编译错误。
但是这样,我们就不能再block中保存值了。
解决方法:
1. c中有一个变量,允许block改写值。
静态变量
静态全局变量
全局变量
在Block中,访问静态全局变量/全局变量没有什么改变,可以直接使用。
静态变量中,转换后的函数原本就设置在含有block语法的函数外,所以无法从变量作用域访问。
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; }; return 0; }
转换后:
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; global_val *= 1; static_global_val *= 2; (*static_val) *= 3; } static struct __main_block_desc_0 { unsigned long reserved; unsigned long Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }; int main() { static int static_val = 3; blk = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &static_val); return 0; }
全局变量和静态全局变量并没有转换,而静态变量的指针对他进行了访问。 将静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数,并保存。这是超出作用域后使用变量的最简单的方法。
2、使用__block说明符
__block存储域类说明符 __block storage-class-specifier
c中的存储域类说明符:
typedef extern static auto register
这里的__block说明符类似于static 、 auto和register说明符, 他们勇于指定变量值设置到那个存储区域中。
__block int val = 10; void (^blk)(void) = ^{val = 1;};
编译后:
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; __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; (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(&dst->val, src->val, BLOCK_FIELD_IS_BYREF); } static void __main_block_dispose_0(struct __main_block_impl_0 *src) { __Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF); } static struct __main_block_desc_0 { unsigned long reserved; unsigned long 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() { __Block_byref_val_0 val = { 0, &val, 0, sizeof(__Block_byref_val_0), 10 }; blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000); return 0; }
代码很长, 这个__block变量val是怎样转换过来的 ?
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
他变成了结构体。 __block变量也同Block一样变成了__Block_byref_val_0结构体类型的自动变量, 即在栈上生成的__Block_byref_val_0结构体实例。 初始化为10,
这个值也出席那种结构体实例的初始化中:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
这意味着 该结构体持有相当于原自动变量的成员变量。
上面结构体中的val相当于原来自动变量的成员变量。
来看给__block变量赋值的代码:
^{val = 1;}
转换后:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
}
在给静态变量赋值的时候 使用了指针。 而向__block变量赋值要更加复杂。
Block的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
在__Block_byref_val_0结构体实例的成语变量__forwarding持有指向该实例自身的指针。通过__forwarding访问成员变量val。(这里的val是该实例自身持有的变量,它相当于原自动变量)
关于__forwarding会在续集继续讨论。