block 的设计非常有意思
我会先把各种情况下的 block 转成 c++,看看它的底层实现,再做总结,以防自己忘记😄
转换命令:
clang -x objective-c -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
基础情况
int b = 100;
NSObject *c = [[NSObject alloc] init];
// global
EmptyBlock block = ^{
printf("");
};
[self printBlockClass:block];
// stack
[self printBlockClass:^{
printf("%ld", b);
}];
// malloc
block = ^{
printf("%ld", c);
};
[self printBlockClass:block];
最基本的 block 是全局 block,不引用局部变量,可能引用全局变量,也可能不引用任何变量,存放在数据段中:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
printf("");
}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
可以看到 global 类型的 block 的初始化函数中,将 isa 设置成 StackBlock,但实际看汇编代码就可以知道,根本就不会走初始化函数,从数据段中取出来就直接用了。
引用了局部变量的 block
引用了基础类型的局部变量:
struct __ViewController__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_1* Desc;
int b;
__ViewController__viewDidLoad_block_impl_1(void *fp, struct __ViewController__viewDidLoad_block_desc_1 *desc, int _b, int flags=0) : b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_1(struct __ViewController__viewDidLoad_block_impl_1 *__cself) {
int b = __cself->b; // bound by copy
printf("%ld", b);
}
static struct __ViewController__viewDidLoad_block_desc_1 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_1_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_1)};
struct __ViewController__viewDidLoad_block_impl_2 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_2* Desc;
NSObject *__strong c;
__ViewController__viewDidLoad_block_impl_2(void *fp, struct __ViewController__viewDidLoad_block_desc_2 *desc, NSObject *__strong _c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_2(struct __ViewController__viewDidLoad_block_impl_2 *__cself) {
NSObject *__strong c = __cself->c; // bound by copy
printf("%ld", c);
}
static void __ViewController__viewDidLoad_block_copy_2(struct __ViewController__viewDidLoad_block_impl_2*dst, struct __ViewController__viewDidLoad_block_impl_2*src) {
_Block_object_assign((void*)&dst->c, (void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __ViewController__viewDidLoad_block_dispose_2(struct __ViewController__viewDidLoad_block_impl_2*src) {
_Block_object_dispose((void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __ViewController__viewDidLoad_block_desc_2 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_2*, struct __ViewController__viewDidLoad_block_impl_2*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_2*);
} __ViewController__viewDidLoad_block_desc_2_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_2), __ViewController__viewDidLoad_block_copy_2, __ViewController__viewDidLoad_block_dispose_2};
对比可知:
- block 将引用的局部变量放在了自身结构体内
- 不论是基本数据类型还是对象,结构体内的变量的类型跟外部的都一样,比如 int 和 NSObject *
- 对象类型还包含了内存管理修饰符 __strong
- 对象类型还定义了 copy 和 dispose 函数去管理对象的 copy 和 release,在 copy 结构体时会调用,这两个函数被包含在了 desc 结构体中
- stackblock 不持有被引用的对象。因为 stackblock 没有被拷贝,所以被引用对象的 copy 函数也没有调用
__weak 修饰了局部变量的 block
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
NSObject *__weak wc;
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
NSObject *__weak wc = __cself->wc; // bound by copy
printf("%p", wc);
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->wc, (void*)src->wc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->wc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
睁大了眼睛看,其实跟上面的区别很小,就是 wc 的修饰符变成了 __weak
_Block_object_assign/_Block_object_dispose 函数会根据修饰符妥善处理好内存管理的
__block 修饰了局部变量的 block
__block int a = 10;
EmptyBlock block = ^{
printf("%ld", a);
};
a = 20;
转出来的 c++ 代码(desc 结构体,copy/dispose 函数我没有列举):
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
printf("%ld", (a->__forwarding->a));
}
这次居然搞出来一个结构体来包装被引用的局部变量
为什么不直接取地址,我觉得原因有如下:
- 只能将被引用的变量放在堆中,因为不知道 block 什么时候会被释放,放在栈中是不行的。既然放在堆上,堆的分配策略至少会分配16个字节,所以多放点东西,也不会太心疼空间😂
- 另外的原因,接着看
forwarding 什么作用
因为 block 会被拷贝到堆上,这样就有了两个 block 了,为了方便处理,会在拷贝之后,把栈上的 block 中的 forwaring 指向堆上 block 中的 a
外部怎么用被引用的变量?
(a.__forwarding->a) = 20;
外部也是用的被包装后的结构体
__weak 修饰了局部变量的 block
NSObject *c = [[NSObject alloc] init];
__block __weak typeof(c) wc = c;
EmptyBlock block = ^{
wc = [[NSObject alloc] init];
printf("%p", wc);
};
struct __Block_byref_wc_0 {
void *__isa;
__Block_byref_wc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *__weak wc;
};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_wc_0 *wc; // by ref
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_wc_0 *_wc, int flags=0) : wc(_wc->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_wc_0 *wc = __cself->wc; // bound by ref
(wc->__forwarding->wc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
printf("%p", (wc->__forwarding->wc));
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_assign((void*)&dst->wc, (void*)src->wc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {
_Block_object_dispose((void*)src->wc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
有如下变化:
- 被引用变量被包装成结构体之后,结构体中又多出来了 copy/dispose 两个函数变量
- desc 结构体中也有两个 copy/dispose 函数,但是与上面的又不一样
desc 中的内存管理函数是管理 block 结构体中的成员变量 wc 的,block 对 wc 永远都是 strong
被引用变量包装成的结构体中的内存管理函数是处理结构体中的 wc 变量的,此时的 wc 是 __weak
所以把被引用的变量包装成结构体主要是因为这个
总结
对不同类型的 block 进行 copy 时,都做了什么
- Global: 什么也不做
- Malloc: 引用计数加1
- Stack: 复制到堆上
什么时候会 copy block
ARC情况下,编译器会自动根据情况对 block 进行 copy:
- block 作为返回值时
- 赋值给 __strong 类型变量
- Cocoa API 中有 名为UsingBlock的参数时