confusion on __block NSObject *obj and block runtime
我使用
1 2 3 4 5 6 | void func() { __block NSObject *obj = [[NSObject alloc] init]; void (^blk)(void) = ^() { obj = nil; }; } |
我相信当复制块并移动到堆时,堆中的块将保留
生成的c ++代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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)); } |
注意:
复制块时,会调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | void _Block_object_assign(void *destAddr, const void *object, const int flags) { //printf("_Block_object_assign(*%p, %p, %x) ", 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 ", object); _Block_retain_object(object); //printf("done retaining object at %p ", 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 ", destp, src, flags); //printf("src dump: %s ", _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 "); // 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); } |
由于
我相信
我在这里获得了Blocks Runtime的源代码,您可以使用
更新1:
1 2 3 4 | static void _Block_assign_default(void *value, void **destptr) { *destptr = value; } static void (*_Block_assign)(void *value, void **destptr) = _Block_assign_default; |
是的,不是。它没有直接"保留"它。我会解释一下。
你说块会"保留"捕获的对象指针变量,这是因为块保留了捕获的常规(非
但是,在这种情况下,因为它是
(请注意,
另一种思考方式是
所以参考图如下:
1 | block --> holder object --> NSObject object |
该块确实间接保留了对象,但不是直接保留。
更新:所以好像你想知道为什么,如果"持有者对象"将底层指针变量视为强引用,则不会看到"持有者对象"保留并释放
实际上,如果你编译代码,你会看到不同的东西。运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | ; Function Attrs: ssp uwtable define internal void @__Block_byref_object_copy_(i8*, i8*) #0 { %3 = alloca i8*, align 8 %4 = alloca i8*, align 8 store i8* %0, i8** %3, align 8 store i8* %1, i8** %4, align 8 %5 = load i8*, i8** %3, align 8 %6 = bitcast i8* %5 to %struct.__block_byref_obj* %7 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 6 %8 = load i8*, i8** %4, align 8 %9 = bitcast i8* %8 to %struct.__block_byref_obj* %10 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %9, i32 0, i32 6 %11 = load %0*, %0** %10, align 8 store %0* null, %0** %7, align 8 %12 = bitcast %0** %7 to i8** %13 = bitcast %0* %11 to i8* call void @objc_storeStrong(i8** %12, i8* %13) #2 %14 = bitcast %0** %10 to i8** call void @objc_storeStrong(i8** %14, i8* null) #2 ret void } ; Function Attrs: ssp uwtable define internal void @__Block_byref_object_dispose_(i8*) #0 { %2 = alloca i8*, align 8 store i8* %0, i8** %2, align 8 %3 = load i8*, i8** %2, align 8 %4 = bitcast i8* %3 to %struct.__block_byref_obj* %5 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %4, i32 0, i32 6 %6 = bitcast %0** %5 to i8** call void @objc_storeStrong(i8** %6, i8* null) #2 ret void } |
在
1 2 | heap_holder->var = stack_holder->var; stack_holder->var = nil; |
在
1 | heap_holder->var = nil; |
这与"重写器"生成的代码中的内容非常不同。我猜测重写器可能不考虑ARC - 换句话说,它执行MRC - > MRC重写。如果这是一个MRC - > MRC重写,生成的代码确实是正确的,因为
实际上,如果在没有ARC的情况下再次编译它,使用
如果我们看一下用于块代码生成的Clang源代码,在构建byref帮助器的部分
但是如果你看一下Objective-C重写器的来源,生成byref副本和配置助手
好。
感谢@CRD和@newacct的建议,我分成汇编代码并找到一些线索。我将在这里发布汇编代码并进行一些分析。
汇编代码目标是armv7,因此long和指针需要4个字节用于您的信息。
我的第一个目标是找到我的问题中引用的
让我们使用"holder object"代替
1 2 3 4 5 6 7 8 9 | struct __Block_byref_obj_0 { void *__isa; // address 0 __Block_byref_obj_0 *__forwarding; // address 4 int __flags; // address 8 int __size; // address 12 void (*__Block_byref_id_object_copy)(void*, void*); // address 16 void (*__Block_byref_id_object_dispose)(void*); // address 20 NSObject *obj; // address 24 }; |
如注释所示,
现在我在Block.m中发布部分主要功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | @ BB#0: push {r4, r5, r6, r7, lr} add r7, sp, #12 push.w {r8, r10, r11} sub.w r4, sp, #64 bfc r4, #0, #4 mov sp, r4 vst1.64 {d8, d9, d10, d11}, [r4:128]! vst1.64 {d12, d13, d14, d15}, [r4:128] sub sp, #160 movs r0, #0 .loc 1 27 23 prologue_end Ltmp3: str r0, [sp, #80] add r1, sp, #80 str r1, [sp, #84] mov.w r2, #838860800 str r2, [sp, #88] movs r2, #28 str r2, [sp, #92] movw r2, :lower16:(___Block_byref_object_copy_-(LPC0_2+4)) movt r2, :upper16:(___Block_byref_object_copy_-(LPC0_2+4)) LPC0_2: add r2, pc str r2, [sp, #96] movw r2, :lower16:(___Block_byref_object_dispose_-(LPC0_3+4)) movt r2, :upper16:(___Block_byref_object_dispose_-(LPC0_3+4)) LPC0_3: add r2, pc str r2, [sp, #100] add.w r2, r1, #24 .loc 1 27 30 is_stmt 0 movw r3, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4)) movt r3, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_4+4)) LPC0_4: add r3, pc ldr r3, [r3] movw r9, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4)) movt r9, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_5+4)) LPC0_5: add r9, pc ldr.w r9, [r9] str r0, [sp, #40] @ 4-byte Spill mov r0, r3 str r1, [sp, #36] @ 4-byte Spill mov r1, r9 str r2, [sp, #32] @ 4-byte Spill blx _objc_msgSend .loc 1 27 29 movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4)) movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4)) LPC0_6: add r1, pc ldr r1, [r1] blx _objc_msgSend .loc 1 27 23 str r0, [sp, #104] .loc 1 27 5 ldr r0, [sp, #32] @ 4-byte Reload .loc 1 28 25 is_stmt 1 movw r1, :lower16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4)) movt r1, :upper16:(L__NSConcreteStackBlock$non_lazy_ptr-(LPC0_7+4)) LPC0_7: add r1, pc ldr r1, [r1] str r1, [sp, #52] mov.w r1, #-1040187392 str r1, [sp, #56] ldr r1, [sp, #40] @ 4-byte Reload str r1, [sp, #60] movw r2, :lower16:(___func_block_invoke-(LPC0_8+4)) movt r2, :upper16:(___func_block_invoke-(LPC0_8+4)) LPC0_8: add r2, pc str r2, [sp, #64] movw r2, :lower16:(___block_descriptor_tmp-(LPC0_9+4)) movt r2, :upper16:(___block_descriptor_tmp-(LPC0_9+4)) |
看看这些代码:
1 2 3 4 5 6 7 8 | movw r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4)) movt r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_.2-(LPC0_6+4)) LPC0_6: add r1, pc ldr r1, [r1] blx _objc_msgSend .loc 1 27 23 str r0, [sp, #104] |
现在让我们关注
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | push {r7, lr} mov r7, sp sub sp, #12 movs r2, #0 str r0, [sp, #8] str r1, [sp, #4] ldr r0, [sp, #8] mov r1, r0 adds r1, #24 ldr r3, [sp, #4] mov r9, r3 add.w r9, r9, #24 ldr r3, [r3, #24] str r2, [r0, #24] mov r0, r1 mov r1, r3 str.w r9, [sp] bl _objc_storeStrong movs r1, #0 ldr r0, [sp] bl _objc_storeStrong add sp, #12 pop {r7, pc} |
为了提醒你
因此在
1 2 3 4 | str r0, [sp, #8] str r1, [sp, #4] ldr r0, [sp, #8] mov r1, r0 |
它将r0存储到sp + 8,将r1存储到sp + 4,将r0移到r1。所以r1 = r0 = dst持有者的地址。
1 2 3 4 | adds r1, #24 ldr r3, [sp, #4] mov r9, r3 add.w r9, r9, #24 |
然后它将r1 + 24添加到r1,所以r1将obj的地址存储在dst holder中,将[sp + 4]的值加载到r3,所以r3存储src holder的地址,将r3移到r9,添加r9 + 24到r9,所以r9在src持有者中有obj的地址。
1 2 3 4 5 | ldr r3, [r3, #24] str r2, [r0, #24] mov r0, r1 mov r1, r3 str.w r9, [sp] |
它将[r3 + 24]中的值加载到r3,因此r3将obj存储在src持有者中,将r2存储到r0 + 24,r2为零,因此dst holder中的obj为NULL。然后将r1移到r0,r0在dst holder中有obj的地址,将r3移到r1,所以r1将obj存储在src holder中。
1 2 3 4 | bl _objc_storeStrong movs r1, #0 ldr r0, [sp] bl _objc_storeStrong |
要知道什么是
当
然后它将0分配给r1,将[sp]加载到r0,[sp]将obj的地址存储在src holder中。
结论:我不知道为什么它会在源代码持有者中释放obj并分配给nil,不应该在
但是,目标持有者的obj确实强烈提到
我认为您在分析中缺少的是您没有看到为ARC插入的调用,而是您正在查看语言级别分配。
在
1 | NSObject *obj; |
没有任何明确强大的所有权限定词。当我运行类似的测试时,Clang输出:
1 | NSObject *__strong obj; |
包括明确的所有权限定符。
如果查看汇编级代码,您会看到在各个地方都有针对ARC内存管理例程的调用。
因此,您所看到的简单任务可能实际上被编译为强大的存储 - 即释放任何现有的引用,保留新的引用。这当然与原始Objective-C完全相同,您将ARC语义"读入"作为语言语义的组成部分。
HTH