block copy
大多数情况下,编译器会自动生成赋值到堆上的代码,但是有以下情况例外:
- 向方法或函数中传递 block 时
但是如果在方法或函数中适当地复制了传递过来的参数,那么就不必在调用该方法或函数前手动复制。以下方法或函数不用手动复制:
- cocoa 框架中的方法且方法名含有
usingBlock
- Grand Central Dispath(
GCD
) 的 API - 将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
- 将 block 作为返回值时
block __forwarding
被 __block
修饰的变量,会生成如下结构体:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
我们注意到,__block
变量生成的结构体多了成员变量 __forwarding
,这是原因:不管 __block
变量配置在栈上还是堆上,都能够正确的访问该变量。
void test() {
__block int val = 0;
void (^blk)() = [^(){
++val;
} copy];
++val;
blk();
}
此代码中在 Block 语法的表达式中使用初始化后的 __block 变量:
{++val;}
然后在 Block 语法之后使用与 Block 无关的变量:
++val
以上两种源代码均可以转换为如下形式:
++(val.__forwarding->val);
当把 __block
变量生成的结构体实例从栈复制到堆上时,会将成员变量 __forwarding
的值替换为复制目标堆上的 __block
变量(生成的结构体实例),如下图所示;
block 循环引用
同样使用 __weak
关键字是可以的,特别的 __weak
与 __block
可以同时使用。使用 __block
和避免循环引用没有直接关系。
特别的,在本书上有这么说:
在不能使用
__weak
修饰符的环境中使用__unsafe_unretained
修饰符(且不必担心访问悬垂指针(访问已经被释放的对象))。
但实际测试中,访问悬垂指针依旧会导致 crash,因为当指向对象被释放时,并没有将指针指向 nil。
另外,ARC(自动引用计数) 无效时,__block
说明符被用来避免 Block 中的循环引用。这是由于当 Block 从栈复制到堆时,若 Block 使用的变量为附有 __block
说明符的 id 类型或对象类型的自动变量(存储在栈中的变量),不会被 retain。若 Block 使用的变量为没有 __block
修饰的 id 类型或对象类型自动变量则会被 retain。
(下面代码为 ARC 有效,当 ARC 无效时, __block 变量不必置 nil。)
typedef void (^blk_t)(void);
@interface MyObject : NSObject {
blk_t _blk;
}
@end
@implementation MyObject
- (instancetype)init
{
self = [super init];
if (self) {
__block id tmp = self;
_blk = ^{
NSLog(@"%@", tmp);
tmp = nil;
};
}
return self;
}
- (void)execBlock {
!_blk ? : _blk();
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
id o = [MyObject new];
[o execBlock];
}
}
上面代码没有引起循环引用。但是如果调用 execBlock 实例方法,即不执行 _blk 引用的 Block,便会引起循环引用。 这是因为生成 MyObject
实例对象时,会造成循环引用:
-
MyObject
实例对象持有 Block - Block 持有
__block
变量生成的结构体实例 -
__block
变量生成的结构体实例持有MyObject
实例对象
但是使用 __block
能控制对象的持有时间,可以根据 Block 的用途选择使用 __block
、__weak
、__unsafe_unretained
修饰符来避免循环引用。
使用 __block 修饰变量的 cpp 代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __test7_block_impl_0 {
struct __block_impl impl;
struct __test7_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__test7_block_impl_0(void *fp, struct __test7_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 __test7_block_func_0(struct __test7_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __test7_block_copy_0(struct __test7_block_impl_0*dst, struct __test7_block_impl_0*src) {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __test7_block_dispose_0(struct __test7_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __test7_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test7_block_impl_0*, struct __test7_block_impl_0*);
void (*dispose)(struct __test7_block_impl_0*);
} __test7_block_desc_0_DATA = { 0, sizeof(struct __test7_block_impl_0), __test7_block_copy_0, __test7_block_dispose_0};
void test7() {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__test7_block_impl_0((void *)__test7_block_func_0, &__test7_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test7();
}
}