这篇文章是自己看源代码以及一些文章总结出来的,如有错误希望有大神指正。
需要先知道这些
Block 在底层是以结构体的形式实现的,Block 的函数体会被分离出来成为一个单独的 C 函数,最后在调用 Block 的时候是以函数指针的形式进行调用。
同时 Block 可能会捕获一些值,保存到结构体中。Block 能够捕获自动变量(不能修改)、静态局部变量(可以修改)。全局变量因为作用域的原因不会被 Block 捕获,可以直接使用。
Block 有3种类型:_NSConcreteStackBlock、_NSConcreteMallocBlock 以及 _NSConcreteGlobalBlock。
比如说有一段代码:
int val = 10;
void (^block)() = ^{ NSLog(@"%d", val); };
block();
用 C++ 重写,简化之后:
struct __main_block_impl_0 {
void *isa;
void *FuncPtr;
int val;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
NSLog(@"%d", val);
}
int main(int argc, const char * argv[]) {
int val = 10;
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, val);
struct __main_block_impl_0 *block = &tmp;
(*block->impl.FuncPtr)(block);
return 0;
}
Block 的实现
在 Block_private.h 中有关于 Block 的实现:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
可以与 C++ 重写后的结构体比较一下,其实是一样的。用 Block_layout 来表示上一个例子,其实就是会编译成这样(简化):
struct __block_literal_1 {
void *isa; // = _NSConcreteStackBlock
void (*invoke)(struct __block_literal_1 *); // = __main_block_func_0
struct __block_descriptor_1 *descriptor;
const int val; // 10
}
static struct __block_descriptor_1 {
unsigned long int Block_size; // = sizeof(struct __block_literal_1)
}
当一个 Block 被初始化的时候,有以下几点:
-
invoke函数指针会指向一个 C 函数,比如上文中的__main_block_func_0,这个函数以Block结构体作为第一个参数,剩下的参数就是调用Block的时候传递的参数(如果有的话)。 -
isa会被赋值为_NSConcreteStackBlock。如果Block定义在全局的区域或者Block仅仅捕获了全局变量(或静态局部变量),则isa被赋值为_NSConcreteGlobalBlock。 - 如果存在相关的
copy_helper方法和dispose_helper方法,则会与copy和dispose这两个函数指针联系起来(后文 Copy & Dispose 章节)。
另外我找到这样一句话是关于 _NSConcreteStackBlock 和 _NSConcreteGlobalBlock:
that is, a different address is provided as the first value and a particular (1<<28) bit is set in the
flagsfield, and otherwise it is the same as for stack basedBlockliterals. This is an optimization that can be used for anyBlockliteral that imports noconstor__blockstorage variables.
我有点想不通为什么捕获全局变量(或静态局部变量)的 Block 就是 _NSConcreteGlobalBlock,看了上面这段话之后还是很模糊(技术不行英文又渣),如果有大神知道的话期待回复。
特别要注意的是,如果 Block 没有捕获变量,或者仅仅捕获了全局变量(或静态局部变量),用 clang 重写之后,它的 isa 指针的值依然是 _NSConcreteStackBlock,只有定义在全局区域的 Block 用 clang 重写之后是 _NSConcreteGlobalBlock。但是用
Xcode 运行的话,以上情况都是 _NSConcreteGlobalBlock,我想这可能是因为编译器的处理,这方面也不是很清楚。
__block 的实现
在 Block_private.h 中有关于 __block 的实现:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
};
用 C++ 重写之后的结构体也是一样的:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
};
当一个 __block 结构体被初始化的时候,原始值会成为该结构体的一个成员变量,同时其中的 __forwarding 指针指向结构体自己。
与 Block 初始化相同的是,如果必要的话也会存在 copy_helper 和 dispose_helper 方法分别与 byref_keep 和 byref_destroy 这两个函数指针联系起来。
Copy & Dispose
在 ARC 的环境下,_NSConcreteStackBlock 类型的 Block 很多情况下都会从栈拷贝到堆,变成 _NSConcreteMallocBlock。
假如有以下代码:
typedef void (^block)(void);
block func() {
int val = 3;
block blk = ^{ NSLog(@"%d", val); };
return blk;
}
转换之后会有以下两个方法(我是这样转换的:clang -fobjc-arc -S filename):
_objc_retainBlock_objc_autoreleaseReturnValue
在第一个方法中调用的就是 Block_copy 方法。
Copy
对象
如果 Block 引用了一个对象,可以看看 C++ 重写之后的代码:
NSObject *obj = [NSObject new];
void (^block)() = ^{
NSLog(@"%@", obj);
};
block(); struct __main_block_impl_0 {
NSObject *__strong obj;
};
static struct __main_block_desc_0 {
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
不仅 block 强引用了 obj,还多出了两个函数指针:copy 和 dispose。
__main_block_copy_0 和 __main_block_dispose_0 这两个方法用来管理 obj 的生命周期。这两个函数中有 _Block_object_assign 和 _Block_object_dispose 两个函数。在 runtime.c 中有它们的实现。
现在梳理一下这个例子中 Block 从栈拷贝到堆的调用栈:
- 调用
Block_copy,将栈上的Block拷贝到堆,isa赋值为_NSConcreteMallocBlock - 调用
_Block_call_copy_helper - 调用函数指针
copy指向的函数,在这里是__main_block_copy_0 - 调用
_Block_object_assign,BLOCK_FIELD_IS_OBJECT作为参数flags表示引用的是一个对象 - 调用
_Block_retain_object,但是这个函数没有任何实现。我觉得可以这样理解:在这个例子中,obj本身就存在于堆内存中,Block仅仅是通过值传递的形式拿到了指针,所以没必要从栈拷贝到堆。
__block 变量(非对象)
如果 Block 捕获了用 __block 修饰的变量,用 C++ 重写之后出现类似的情况:
__block int i = 10;
void (^block)() = ^{
NSLog(@"%d", i);
}; struct __main_block_impl_0 {
__Block_byref_i_0 *i; // by ref
};
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 {
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
}
__block 变量成为了 Block 结构体的成员,flags 的值从 BLOCK_FIELD_IS_OBJECT
变为了 BLOCK_FIELD_IS_BYREF,因为现在的结构体使用的是 __block 变量。
这个例子中 Block 从栈拷贝到堆的过程与上个例子不同的是在 _Block_object_assign 中的调用,调用了 _Block_byref_copy 函数。在该函数中:
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
这条语句从堆内存开辟了一块空间,将 __block 变量的结构体从栈拷贝到堆。
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
这两条语句保证了无论是在 Block 内访问 __block 变量还是在 Block 外访问,我们访问到的都是同一个变量。
__block 变量(对象)
如果 Block 引用的是一个 __block 修饰的对象:
__block NSObject *obj = [NSObject new];
void (^block)() = ^{
NSLog(@"%@", obj);
}; struct __Block_byref_obj_0 {
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__strong obj;
};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
在这个例子的调用栈中,区别在于 _Block_byref_copy,会进入到这个分支:
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
(*src2->byref_keep)(copy, src);
}
最后调用的是 __Block_byref_id_object_copy 指向的函数,也就是 __Block_byref_id_object_copy_131。_Block_object_assign 函数中的 131 参数其实是 BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,前半部分的意思是此次调用来自
__block 结构体的 byref_keep 函数指针。
除了上文提到的 BLOCK_FIELD_IS_BYREF 和 BLOCK_FIELD_IS_OBJECT,还有另外一个值是 BLOCK_FIELD_IS_BLOCK。这个值表示在 Block 中引用了其他的 Block。
Dispose
释放一个 Block 的调用栈其实与 copy 类似。
- 首先会调用
_Block_release函数,如果是_NSConcreteGlobalBlock类型的Block则不需要释放 - 调用
_Block_call_dispose_helper - 访问
Block结构体中的dispose成员变量,调用其指向的函数,比如是__main_block_dispose_0 - 最后调用
_Block_object_dispose
简单说说循环引用
首先是第一种情况:
__weak __typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@", weakSelf.property);
};
我们可以看到 block 确实是对 self 只有弱引用。
struct __Rectangle__addBlock_block_impl_0 {
struct __block_impl impl;
struct __Rectangle__addBlock_block_desc_0* Desc;
Rectangle *__weak weakSelf;
};
第二种情况:
__weak __typeof(self) weakSelf = self;
self.block = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", weakSelf.property);
};
在这里,block 在内部新建了一个 strongSelf 对 self 进行了一次强引用,但是 block 本身并没有强引用。