我使用 clang -rewrite-objc Block.m
生成 Block.m
的 C++ 代码。
Block.m
中的代码是基于 ARC 的:
void func() {
__block NSObject *obj = [[NSObject alloc] init];
void (^blk)(void) = ^() {
obj = nil;
};
}
我认为当代码块被复制并移动到堆上时,堆中的代码块会保留obj。但是在深入研究代码块运行时的源代码后,我得到了相反的结果。
生成的c++代码:
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);
}
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __func_block_impl_0 {
struct __block_impl impl;
struct __func_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__func_block_impl_0(void *fp, struct __func_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __func_block_func_0(struct __func_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj) = __null;
}
static void __func_block_copy_0(struct __func_block_impl_0*dst, struct __func_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __func_block_dispose_0(struct __func_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __func_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __func_block_impl_0*, struct __func_block_impl_0*);
void (*dispose)(struct __func_block_impl_0*);
} __func_block_desc_0_DATA = { 0, sizeof(struct __func_block_impl_0), __func_block_copy_0, __func_block_dispose_0};
void func() {
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void (*blk)(void) = ((void (*)())&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
}
注意:33554432
是 BLOCK_HAS_COPY_DISPOSE
,570425344
是 BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR
。
当块被复制时,会调用__func_block_copy_0
来处理它捕获的变量,在这种情况下,它会复制(__Block_byref_obj_0)obj
,将obj->forwarding更改为副本__Block_byref_obj_0
等等,所有这些操作都发生在_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
中。
_Block_object_assign
的源代码:
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
// (this test must be before next one)
else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
// copying a Block declared variable from the stack Block to the heap
_Block_assign(_Block_copy_internal(object, flags), destAddr);
}
// (this test must be after previous one)
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
//printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
//printf("src dump: %s\n", _Block_byref_dump(src));
if (src->forwarding->flags & BLOCK_IS_GC) {
; // don't need to do any more work
}
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
//printf("making copy\n");
// src points to stack
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// assign byref data block pointer into new Block
_Block_assign(src->forwarding, (void **)destp);
}
由于
flag
是BLOCK_FIELD_IS_BYREF
,所以执行_Block_byref_assign_copy
函数,该函数为__Block_byref_obj_0
分配了一段内存,并进行了一些赋值操作,最后它将调用(*src->byref_keep)(copy, src)
,该指针指向__Block_byref_id_object_copy_131
。正如我们在这个函数中所看到的那样,只有一行代码:_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
,其中131是BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER
,"(char*)dst+40"是BLOCK_FIELD_IS_BYREF
(copy)的地址,因此它将调用_Block_assign((void *)object, destAddr)
函数,这个函数只是一个简单的赋值操作*destAddr = object;
,没有保留引用!!!我认为应该对
obj
进行保留,但源代码似乎没有对其进行保留。我真的很困惑。您可以在这里获取Blocks Runtime的源代码,使用
svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
命令即可。static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
obj=nil
的作用:释放旧值(在您的代码中是 obj),分配新值(在您的代码中是 nil),保留新值(在您的代码中是[nil retain]
)。如果您的信念是正确的,那么这将按设计工作,并且会泄漏。再次生成代码,这次除了分配给 nil 之外,尝试其他任何操作。 - danhobj
)。它被块保留,因为块引用它(而不是因为 __block 修饰符)。代码将其设置为 nil,因此需要释放。如果将其设置为 nil 没有释放它,那么它将泄漏。 - danh