Block实现原理
- Block是带有自动变量值的匿名函数;
-
带有自动变量值
在Block中表现为截获自动变量值
; - 自动变量值截获只能保存执行Block语法瞬间的值,保存后就不能改写该值;
- 向截获的自动变量赋值会产生编译错误,但使用截获的值不会产生任何问题;
- 使用
__block
说明符的自动变量可在Block中赋值,该变量称为__block
变量; - Block其实就是OC对象;
一、Block的实质
使用 clang -rewrite-objc main.m
将包含Block语法的源代码转换成C++的源代码。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
printf("-------------Block------------------\n");
};
blk();
return 0;
}
转换后的C++源码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; //指向Block的实现
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("-------------Block------------------\n");
}
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 argc, const char * argv[]) {
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;
}
源码中的Block语法:
^{ printf("-------------Block------------------\n"); };
变换后的源代码中也含有相同的表达式:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("-------------Block------------------\n");
}
参数__cself
是指向__main_block_impl_0
结构体的指针变量。
struct __main_block_impl_0 *__cself;
该结构体的声明如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
第一个成员变量是impl
,其结构体如下:
struct __block_impl {
void *isa;
int Flags; // 标志
int Reserved; // 预留区
void *FuncPtr; // 函数指针
};
第二个成员变量是Desc
指针,其__main_block_desc_0
结构体如下:
static struct __main_block_desc_0 {
size_t reserved; // 预留区
size_t Block_size; // Block大小
} ;
下面是包含这些结构体的__main_block_impl_0
结构体的构造函数:
__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 temp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
struct __main_block_impl_0 *blk = &temp;
Block的调用转化为以下源码:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
将上面的转换部分删除之后,具体如下:
(*blk->impl.FuncPtr)(blk);
这就是简单的使用函数指针调用函数。由Block语法转换的__main_block_func_0
函数的指针被赋值成员变量FuncPtr
中。此外,__main_block_func_0
函数的参数
__cself
指向Block值。在调用该函数的源码中可以看出Block正是作为参数进行了传递。
成员变量isa
的初始化如下:
impl.isa = &_NSConcreteStackBlock;
_NSConcreteStackBlock
相当于class_t
结构体实例。将Block作为OC对象处理时,关于该类的信息位于_NSConcreteStackBlock
中。
Block的实质就是Block为Objective-C对象。
二、捕获自动变量值
如下示例:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
int dmy = 250;
int val = 10;
const char * fmt = "val = %d \n";
void (^blk)(void) = ^{
printf(fmt,val);
};
blk();
return 0;
}
Block捕获局部变量val
和fmt
的值,转换之后的源码如下:
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 argc, const char * argv[]) {
int dmy = 250;
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));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
由上面的源码可知,Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0
结构体中。而没有使用的自动变量不会被追加。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
};
下面来看Block匿名函数的实现,初始源码如下:
void (^blk)(void) = ^{
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);
}
由上可知:所谓截获自动变量值
意味在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。
三、全局变量、全局静态变量和局部静态变量
#import <Foundation/Foundation.h>
// 全局变量
int global_val = 1990;
// 全局静态变量
static int static_global_val = 1011;
int main(int argc, const char * argv[]) {
// 局部静态变量
static int static_val = 1994;
void (^blk)(void) = ^{
global_val *= 2;
static_global_val *= 3;
static_val *= 4;
};
blk();
return 0;
}
在上面的示例中,定义了三个变量,一个全局变量global_val
,一个静态全局变量 static_global_val
,一个局部静态变量static_val
,然后在Block语法中修改这三个变量的值。该源码转换后如下:
int global_val = 1990;
static int static_global_val = 1011;
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 *= 2;
static_global_val *= 3;
(*static_val) *= 4;
}
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 argc, const char * argv[]) {
static int static_val = 1994;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
对全局变量global_val
和静态全局变量 static_global_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) *= 4;
}
使用局部静态变量static_val
的指针对其进行访问,将static_val
的指针传递给__main_block_impl_0
结构体的构造函数并保存。
四、__block
存储域类说明符
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
__block int val = 1994;
void (^blk)(void) = ^{
val += 25;
};
blk();
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) += 25;
}
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(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1994};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
由上面的源码可知,__block变量被转换成__Block_byref_val_0
结构体类型的自动变量,即栈上生成的__Block_byref_val_0
结构体实例。该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
下面是给__block变量赋值的源码转换:
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) += 25;
}
在给Block中的局部静态变量赋值时,使用了指向该静态变量的指针。而向 __block变量赋值则更复杂。Block的__main_block_impl_0
结构体实例持有指向 __block变量的__Block_byref_val_0
结构体实例的指针。
__Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向该实例自身的指针。通过__forwarding
访问成员变量val,可以保证 __block变量无论配置在栈上还是堆上都可以正确的访问__block变量。这是因为 __block变量的实例在栈上时,__forwarding
是指向自身的指针,当 __block变量从栈上拷贝到堆上时,__forwarding
将指向复制到堆上的__block变量实例。
__block变量的__Block_byref_val_0
结构体并不在Block的__main_block_impl_0
结构体中,是为了在多个Block中使用 __block变量。