【发布时间】:2016-05-02 23:48:09
【问题描述】:
我使用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。但是在深入挖掘block runtime的源码后,却得到了相反的结果。
生成的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。
当block被复制时,__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,这个函数malloc内存为__Block_byref_obj_0的副本并做一些赋值,最后它会调用指向__Block_byref_id_object_copy_131的(*src->byref_keep)(copy, src) ,正如我们在这个函数中看到的,只有一行代码:
_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); 而这个函数只是做一个赋值@ 987654349@,不保留!!!
我认为应该保留obj,但源代码似乎没有保留它。我真的很困惑。
我得到了Blocks Runtime here的源代码,你可以使用svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt获取它。
更新 1:
static void _Block_assign_default(void *value, void **destptr) {
*destptr = value;
}
static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default;
【问题讨论】:
-
为什么你认为应该保留obj?如果是,然后对它的唯一引用设置为 nil,它不会被泄露吗?
-
@danh 我读了很多文章来支持我的观点,this 就是其中之一。
-
考虑一下强大的 setter
obj=nil做了什么:释放旧值(在您的代码中为 obj),分配新值(在您的代码中为 nil),保留新值(@ 987654354@ 在您的代码的情况下)。按设计工作,如果您的信念是真实的,就会泄漏。再次生成代码,这次做任何事情,但分配给 nil。 -
@danh 抱歉,泄露了什么?您不认为该块会保留对 obj 的强引用吗?
-
块前分配的对象(简称
obj)。它被块保留,因为块引用它(不是因为 __block 限定符)。它被释放是因为代码将它设置为 nil。如果将它设置为 nil 没有释放它,它就会被泄露。
标签: ios objective-c block