一些安排和感想
从上一篇的ARC的实现细节开始,会陆续去研究一些老生常谈的话题,诸如:Block,Runtime,GCD,Runloop,Animation等知识点
,并生成一些文章.目的在于巩固已有知识,同时希望能帮到一些"有缘人".
从一段代码开始
先看一段代码:
int main(){
void (^block)(void) = ^{
printf("block");
};
block();
return 0;
}
这段代码的作用很简单:在block内进行简单的打印.但这个过程中到底发生了什么呢?在Terminal我们输入clang -rewrite-objc + 这个文件
,我们可以得到如下的C++源码(过滤一些代码后):
// block 的结构体
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; // block类型
impl.Flags = flags;
impl.FuncPtr = fp; // 实际实现的方法指针
Desc = desc;
}
};
// 这个函数就是最终执行的函数:打印内容
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block");
}
// 描述信息:block大小,版本等
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(){
// 生成一个block变量,这个变量是通过__main_block_impl_0内的构造方法生成的
// 传入了实际方法实现__main_block_func_0的地址,和描述信息__main_block_desc_0_DATA的地址
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 上一步之后__block_impl的FuncPtr指针已经指向了__main_block_func_0的地址
// 因为__main_block_func_0方法需要传入__main_block_impl_0类型的变量,所以直接传入上面的block变量即可完成函数调用.
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
以上代码中注释了一些关键信息,充分说明了block本质是什么,总结一下:
第1步:首先通过构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
生成了一个__main_block_impl_0
类型的变量block,block内的FuncPtr
指针指向了__main_block_func_0
的地址.
第2步:__main_block_func_0
函数又有一个__main_block_impl_0
类型的形参,所以这里实际调用如下:
(*block->impl.FuncPtr)(block)
Block也是一个对象
从上面的C++源码中我们不难发现这个结构体__block_impl
:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
我们对比一下objc_class
结构体class_t
(在runtime源码的版本为437.1中的runtime/objc-runtime-new.h
):
typedef struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
class_rw_t *data;
} class_t;
通过对比我们可以发现,一般对象和block都有一个isa
指针成员变量,所以这里可以理解block是一个对象,并且上述block的对象类型为:
_NSConcreteStackBlock
.
复杂一点的block
以上的block只是最简单的block,这里看一个稍微复杂一点的block:
int main(){
int val1 = 123;
int val2 = 456;
void (^block)(void) = ^{
printf("val1:%d",val1);
};
val1 = 789;
block();
return 0;
}
这里打印的结果大家想必都猜得出来:val1=123
,而不是789.这是为什么呢?
转换后的C++代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val1; // 将使用的val1作为成员变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val1, int flags=0) : val1(_val1) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val1 = __cself->val1; // bound by copy
printf("val1:%d",val1);
}
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 val1 = 123;
int val2 = 456;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val1));
val1 = 789;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
对比之前的block的代码,这里稍微复杂了一点,因为涉及到了对于自动变量val1
的截取.
我们可以看到在__main_block_impl_0
结构体中,val1
被当做了一个成员变量保存在了结构体中,同时实现函数__main_block_func_0
中,打印的方式为
int val1 = __cself->val1; // __cself就代表__main_block_impl_0这个结构体变量
printf("val1:%d",val1);
我们再仔细观察一下这里:
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA, val1));
这里的val1
采用的值传递,而非地址传递.所以可以解释为何后面val1 = 789
,并没有更改初始值.而且我们也发现val2
这个变量未曾使用到,所以在C++源码中也不会被用到.
__block 修饰符
如果我们想更改val1
,这里可以用__block
修饰符来声明变量:
__block int val1 = 123;
但是一旦这么声明之后,转换之后的C++代码变得稍微复杂了一些:
struct __Block_byref_val1_0 {
void *__isa;
__Block_byref_val1_0 *__forwarding;
int __flags;
int __size;
int val1;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val1_0 *val1; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val1_0 *_val1, int flags=0) : val1(_val1->__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_val1_0 *val1 = __cself->val1; // bound by ref
printf("val1:%d",(val1->__forwarding->val1));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val1, (void*)src->val1, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val1, 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_val1_0 val1 = {
(void*)0,
(__Block_byref_val1_0 *)&val1,
0,
sizeof(__Block_byref_val1_0),
123
};
int val2 = 456;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val1_0 *)&val1, 570425344));
(val1.__forwarding->val1) = 789;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
很明显我们可以看出源码中多了一个__Block_byref_val1_0
这个结构体,它的作用就是用来保存我们__block
修饰过的变量
struct __Block_byref_val1_0 {
void *__isa;
__Block_byref_val1_0 *__forwarding;// 指向自己的指针
int __flags;
int __size;
int val1; // 保存的成员变量
};
并且在main
函数中,将123
这个值初始化进了__Block_byref_val1_0
结构体变量中.
__attribute__((__blocks__(byref))) __Block_byref_val1_0 val1 = {
(void*)0,
(__Block_byref_val1_0 *)&val1,
0,
sizeof(__Block_byref_val1_0),
123
};
当我们想更改val1
时,这时候因为val1
被存进了__Block_byref_val1_0
类型的结构体中,并且传进__main_block_impl_0
构造方法的是(__Block_byref_val1_0 *)&val1
这个val1
变量地址,所以是地址传递.而这里有一个__forwarding
是指向__Block_byref_val1_0
自己的,所以当设置val1 = 789
的时候,可以通过(val1.__forwarding->val1) = 789
来改变val1
.
为什么会有__forwarding指针
这里涉及到block的作用域.在ARC情况下,编译器通常会进行适当的判断将栈上的block复制到堆里面,这个想必大家都比较清楚.
为什么需要复制到堆里呢?
因为有可能这个block在当前作用域结束的时候,block内的变量还可能需要使用.如果不复制到堆里面,那么后面就无法使用了.所以这里设置了__forwarding
指针指向堆的结构体实例,不管block是在栈上,还是在堆里,都可以通过__forwarding
访问到相应的成员变量.
暂时到这里吧,后面的循环引用想必都十分熟悉了,就不再一一赘述了.