Block 的本质
在 Objective-C Block Part1 中总结了 Block 的一些使用规则,但是为什么要遵循这些规则,还有这些规则是怎么来的? 这就需要探寻 Block 的本质,明白它的实现原理。
注: 我们通过 clang 把包含 Block 的 Objective-C 的代码转换成 C++ 实现代码,以此来分析 Block 的实现原理。通过 clang 重写的代码仅供我们分析和参考,在极少数地方和实际运行时有细微出入。
简单 Block 转换后的代码分析
下面通过 clang -rewrite-objc main.m
把一个简单的 Block 转换成 C++ 代码 main.cpp
:
/** 转换前的 Objective-C 代码: */
int main(int argc, const char * argv[]) {
char *name = "Steve Jobs";
^() {
printf("hello %s :)", name);
}();
return 0;
}
/** 转换后裁剪出的重要代码: */
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;
char *name;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_name, int flags=0) : name(_name) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
char *name = __cself->name; // bound by copy
printf("hello %s :)", name);
}
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[]) {
char *name = "Steve Jobs";
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, name))();
return 0;
}
我们看到 Block 的本质其实就是一个名为 __main_block_impl_0
的结构体。这个结构体包含的主要内容:
-
__block_impl
结构体的变量。 (block 结构体的第一个成员都是__block_impl
, 所以__block_impl
是 block 的基础结构体。 -
__main_block_desc_0
结构体的变量。(__main_block_desc_0
是描述 block 的结构体。 -
可选的 被截获的成员,例如本例的
char *name
,这一块下一节细述。 -
__main_block_impl_0
结构体的构造函数
再来分析一下 __main_block_impl_0
结构体的第一个成员 __block_impl
结构体里的内容:
-
void *isa;
(所有的 OC 对象的都有 isa指针,这也说明 Block 是一个 OC 对象。 -
int Flags;
(用于按 bit 位表示一些 block 的附加信息。 -
int Reserved;
(保留变量。 -
void *FuncPtr;
(block 执行的函数指针,这个函数指针包含的是 OC 里面写在 Block 里面的代码。
__main_block_desc_0
的内容:
-
size_t reserved;
(保留变量 -
size_t Block_size;
(保存 Block 的大小
Block 截获变量
上面一节大概了解了 Block 是个什么东西,这一节则会进行更深入的探索,搞清楚 Block 对各种类型的变量在内部是如何处理。下面通过 clang 转换一个包含各种变量的 Block 来分析这些问题:
/** 转换前的 Objective-C 代码(ARC): */
typedef void(^blk_t)();
static int static_global_val = 1; // 静态全局变量(C
static NSObject *static_global_obj; // 静态全局变量(OC
int global_val = 1; // 全局变量(C
NSObject *global_obj; // 全局变量(OC
int main(int argc, const char * argv[]) {
int automatic_val = 1; // 自动变量(C
NSObject *automatic_obj = [NSObject new]; // 自动变量(OC
__block int __block_val = 1; // __block变量(C
__block NSObject *__block_obj = [NSObject new]; // __block变量(OC
static int static_val = 1; // 静态变量(C
static NSObject *static_obj; // 静态变量(OC
static_global_obj = [NSObject new];
global_obj = [NSObject new];
static_obj = [NSObject new];
blk_t block = ^{
static_global_val = 1;
static_global_obj = [NSArray array];
global_val = 1;
global_obj = [NSArray array];
static_val = 1;
static_obj = [NSArray array];
__block_val = 1;
__block_obj = [NSArray array];
printf("%d,%p", automatic_val, automatic_obj);
//automatic_val = 1; // 报错
//automatic_obj = [NSArray array]; // 报错
};
block();
}
下面是转换成 C++ 的代码, 其中裁剪出重要的内容来显示,代码后面是分析上面问题的答案:
/** 转换后的C++代码(ARC): */
typedef void(*blk_t)();
static int static_global_val = 1;
static NSObject *static_global_obj;
int global_val = 1;
NSObject *global_obj;
struct __Block_byref___block_val_0 {
void *__isa;
__Block_byref___block_val_0 *__forwarding;
int __flags;
int __size;
int __block_val;
};
struct __Block_byref___block_obj_1 {
void *__isa;
__Block_byref___block_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__block_obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
NSObject **static_obj;
int automatic_val;
NSObject *automatic_obj;
__Block_byref___block_val_0 *__block_val; // by ref
__Block_byref___block_obj_1 *__block_obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, NSObject **_static_obj, int _automatic_val, NSObject *_automatic_obj, __Block_byref___block_val_0 *___block_val, __Block_byref___block_obj_1 *___block_obj, int flags=0) : static_val(_static_val), static_obj(_static_obj), automatic_val(_automatic_val), automatic_obj(_automatic_obj), __block_val(___block_val->__forwarding), __block_obj(___block_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref___block_val_0 *__block_val = __cself->__block_val; // bound by ref
__Block_byref___block_obj_1 *__block_obj = __cself->__block_obj; // bound by ref
int *static_val = __cself->static_val; // bound by copy
NSObject **static_obj = __cself->static_obj; // bound by copy
static_global_val = 1;
static_global_obj = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
global_val = 1;
global_obj = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
(*static_val) = 1;
(*static_obj) = objc_msgSend(objc_getClass("NSArray"), sel_registerName("array"));
(__block_val->__forwarding->__block_val) = 1;
(__block_obj->__forwarding->__block_obj) = objc_msgSend(objc_getClass("NSArray"), sel_registerName("new"));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src{...}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {...}
int main(int argc, const char * argv[]) {
int automatic_val = 1;
NSObject *automatic_obj = objc_msgSend((id)objc_getClass("NSObject"), sel_registerName("new"));
__Block_byref___block_val_0 __block_val = {(void*)0,&__block_val,0,sizeof(__Block_byref___block_val_0),1};
__Block_byref___block_obj_1 __block_obj = {(void*)0, &__block_obj, 33554432, sizeof(__Block_byref___block_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"))};
static int static_val = 1;
static NSObject *static_obj;
static_global_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
global_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
static_obj = objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"));
blk_t block = ((void (*)())&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_val, &static_obj, automatic_val, automatic_obj, &__block_val, &__block_obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
1. Block 怎么处理自动变量?
从上面的 C++ 代码中可以看出,在 Block 对应的 __main_block_impl_0
struct 中有和自动变量 automatic_val
automatic_obj
相同的类型的结构体成员。 __main_block_impl_0
struct 的构造方法 __main_block_impl_0
的参数中也会接受 automatic_val
automatic_obj
同类型的参数,然后把其赋值给对应的 strcut 成员。 可以看下在 main
函数中对 __main_block_impl_0
构造函数的调用。 这里有几个值得注意的点:
-
对于
C 基础类型
automatic_val
直接传递的是值1
, 所以 mian 函数中定义的int automatic_val
和 Block 对应结构体中的automatic_val
结构体成员不是同一个变量。通过下面的小例子可以证明:int main(int argc, const char * argv[]) { int automatic_val = 1; blk_t block = ^ { printf("inside in room val:%d \n",automatic_val);}; automatic_val = 2; printf("outside in room val:%d \n",automatic_val); block(); }
下面是上面代码的输出,可以看到在 Block 外改变 val 的值根本影响不到 Block 内的值,因为他们不在同一块内存上
outside in room val:2 inside in room val:1
-
对于
OC 对象类型
automatic_obj
是直接传递指针,和automatic_val
同理,如果在 Block 外新建一个OC 对象类型
的指针,再赋值给automatic_obj
变量也对 Block 内的automatic_obj
结构体成员是没有影响的,因为这两个变量里面此时装的已经是不同的指针了。 但是当它们装的是同一个指针时,是可以通过调用对象的方法来相互影响的, 举个栗子,在 Block 外更改可变数组里的内容是会影响到 Block 内部的可变数组的,因为此时这两个变量是装的同一个指针:int main(int argc, const char * argv[]) { NSMutableArray *automatic_obj = [NSMutableArray arrayWithObjects:@"1", @"2", nil]; blk_t block = ^ { NSLog(@"inside in room obj: %@", automatic_obj); }; [automatic_obj addObject:@"3"]; NSLog(@"outside in room obj: %@", automatic_obj); block(); }
-
在 Block 对应的
__main_block_impl_0
struct 中,修饰automatic_obj
的是 strong 所有权修饰符,所以 Block 会对这个对象进行持有, 为什么系统这么去设计让 Block 持有自动变量? 是因为 Block 能够超出其所在的函数作用域存在,而OC 对象类型
的自动变量在超出函数作用域时就会被释放被释放,Block 此时执行时这个对象已经被销毁了。为了避免这种情况 Block 持有了OC 对象类型
的自动变量。
2. Block 怎么处理 静态全局变量 / 全局变量 / 静态变量 ?
之所以把这三种变量归纳到一起,是因为它们的生命周期在一个程序中会一直存在。 Block 中使用 静态全局变量
和 全局变量
时,因为它们的作用域是全局的,并且是在程序中一直存在的,所以转换后这部分没有任何变化可以直接使用,并且可以在 Block 中重新赋值。 Block 中使用 静态变量
转换后会在 Block 对应的 __main_block_impl_0
struct 中追加这个 静态变量
的类型的指针,然后通过 strcut 的构造函数传递进去,通过这种方式扩大了这个静态变量可访问的作用域, 使其可以在 __main_block_func_0
函数中访问。同时也因为传递的是 静态变量
类型的指针所以具备了重新赋值的能力。
3. Block 怎么处理 __block 修饰的自动变量 ?
上面的例子中 __block
修饰的自动变量 __block_val
__block_obj
在转换后分别变成了 __Block_byref___block_val_0
和__Block_byref___block_val_1
结构体,并且它们第一个成员都是 isa 指针
,这说明它们都是 OC 对象。第二个成员是 __forwarding 指针
目前是指向这个结构体的本身。 这两个结构体分别还包含着对应的 __block
自动变量类型的 结构体成员。
Block 的储存域
因为 Block 对应的结构体第一个成员是 isa 指针
,所以 Block 也是个 OC 对象
,那么 isa 指针
指向的就是 Block 的类了。以前我们接触到的都是 _NSConcreteStackBlock
,其实还有另外两种: _NSConcreteGlobalBlock
_NSConcreteMallocBlock
。 这一节就是来探讨这三种 Block 的不同和作用。
三种 Block 在内存的存储区域
-
_NSConcreteStackBlock
类的 Block 对象是设置栈区上的,超出其所在的函数作用域就会被释放。 -
_NSConcreteGlobalBlock
类的 Block 对象是设置在 .data 区上,在程序运行时永久存在的。 -
_NSConcreteMallocBlock
类的 Block 对象是设置在堆区上。
怎么区分这三种 Block
_NSConcreteGlobalBlock
- 在所有方法外定义的 Block 为
Global Block
- 当 Block 中没有截获自动变量是为
Global Block
_NSConcreteMallocBlock
Malloc Block
是 Stack Block
被执行 copy 操作后得到的。它能让 Block 超出函数/方法的作用域而存在。
_NSConcreteStackBlock
除了上面的情况,剩下的就都是 Stack Block
了。当其所在的函数作用域结束时,这个 Block 就会被回收。
那些情况系统会自动帮你调用 copy 方法
通过 copy 操作可以让 Stack Block
拷贝成 Malloc Block
,但是在一些情况下,系统会自动的帮我们执行 copy 操作:
-
ARC
Block 作为函数返回值返回时会自动对 Block 执行 copy 操作。 -
ARC
下将 Block 赋值给附有__strong
修饰符的变量时。 所以在ARC
下不用调用 copy 操作,直接把它赋值给__strong
变量就可以达到效果,还有对于 Block 类型的 @property 的 attribute 不用写 copy。直接使用默认的__strong
,有太多人在ARC
下还是用 copy 去修饰。 完全没有必要 - 在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递 Block 时。
Block copy 时会发生什么? 会造成什么影响?
大部分的 Block 对象是设置在栈内存上的,为了使 Block 能够超出其函数作用域的范围。可以使用 copy 操作将其从栈内存拷贝到堆内存中。对于 Block 中的 __block
变量一开始它也是配置在栈内存中的,在超出函数作用域时它也会被释放。所以在拷贝 Block 对象到堆内存中时,也会同时拷贝这个 Block 使用的 __block
变量到堆内存中。 当多个 Block 对象同时使用一个 __block
变量时,如果其中有个 Block 已经把 __block
变量拷贝到堆内存上了。后面的 Block 再次对这个 __block
变量执行 copy 操作时只会增加这个 __block
变量的持有。 等 Block 销毁时就会减少 __block
变量的持有。当没有 Block 持有 __block
变量时它就会被回收。这和我们一直使用的引用计数的内存管理方式相同。
为什么要设计 __forwarding 这个东西?
上一节的例子中 __block_val
__block_obj
这两个 __block
变量在被转换后,分别变成了 __Block_byref___block_val_0
__Block_byref___block_val_1
结构体,并且结构体的第二个成员都是 __forwarding
指针。 这个 __forwarding
指针目前是指向自己的。为什么要去设计 __forwarding
指针这个东西?这需要刚才讨论的 Block 对象的拷贝结合在一起看。
__forwarding
指针存在的意义是不管 __block
变量是配置在栈上还是堆上,都能够正确的访问变量。当 Block 对象被拷贝到堆内存中是, __block
变量也被拷贝到堆内存中。那么此时可以同时访问栈上的 __block
变量 和 堆上的 __block
变量。他们都是通过下面的方式访问的, 都是通过 __forarding
指针:
__block_val->__forwarding->__block_val
__block_obj->__forwarding->__block_obj
栈上的 __block
在被拷贝到堆内存时,会改变栈内存的 __forwarding
指针,让其指向堆内存的 __block
变量。 所以通过这个设计让 访问的 __block
变量无论在 Block 中还是 Block外,__block
变量是在堆内存还是栈内存上,访问的都是同一个 __block
变量。
为什么 Block 中的静态变量可以修改, 而自动变量不能修改?
上面讲到 Block 对应的结构体因为保存的是 静态变量
的类型的指针,所以 静态变量
可以在 Block 中被重新赋值。那自动变量为什么不也设计成这样,使其拥有在 Block 中被重新赋值的能力呢? 这是因为自动变量的超出其所在的函数作用域时就会被销毁掉。但是 Block 又可以超出其自身作用域而存在。如果像对待 静态变量
那样去对待 自动变量
,很可能出现的情况就是当 Block 去操作/访问 自动变量
时。自动变量已经被销毁。
小测验
这个关于 Block 的小测试 这是在唐巧的 blog 《谈Objective-C block的实现》 中发现的, 觉得很能考察对 Block 的理解程度,做到全对感觉对 Block 了解就很清楚了。
网友评论