目标C绑定到块的间接指针无法更新直接指针

时间:2016-11-17 17:44:01

标签: objective-c objective-c-blocks objective-c-runtime

我有一个函数将间接指针绑定到一个块中,并返回该块以便以后分配到直接指针中,如下所示:

directPointer is pointing to: 0x0
&directPointer is pointing to: 0x7fff5ce1d060
--- callback execution starts here
indirectPointer is pointing to: 0x7fff5ce1d050
*indirectPointer is pointing to: 0x0
After assignment: indirectPointer is pointing to: 0x7fff5ce1d050
After assignment: *indirectPointer is pointing to: 0x61800001e1d0
--- callback returns here, and the contents of the pointer is lost
after running callback directPointer is pointing to: 0x0
after running callback &directPointer is pointing to: 0x7fff5ce1d060

问题在于,当这一切都编译并运行时,块返回时会立即忘记块的操作。运行[iWillNotDoWhatImSupposedTo]的打印输出是:

{{1}}

有关如何使此回调有效的任何见解?

3 个答案:

答案 0 :(得分:4)

Idali是正确的,因为它与参数是“指向自动释放的指针”并且您正在传递“指向强大的指针”的事实有关。但重要的是要理解为什么这是相关的(因为在大多数情况下它是不相关的),它与引用计数本身无关。

您作为参数传递的是“指向强大的指针”(directPointerSomeClass * __strong,因此&directPointerSomeClass * __strong *),参数是“指向自动释放的指针”。这是如何运作的? ARC通过一种名为“pass-by-writeback”的技术来处理这个问题。

基本上,它的作用是创建一个“autoreleasing”类型的临时变量,为它指定强指针(如果值不为null),然后使用指向此临时变量的指针调用该方法。然后在方法返回后,它将变量的值分配回强变量;大概是这样的东西:

SomeClass * __autoreleasing temporaryPointer = directPointer;
CallbackType cb = [self getCallbackToAssignTo:&temporaryPointer];
directPointer = temporaryPointer;

请注意,如果-getCallbackToAssignTo:方法仅在其自己的执行中同步使用该参数,则SomeClass * __strong *参数与SomeClass * __autoreleasing *参数之间没有区别,因为传递方式-writeback机制确保在执行方法期间所做的任何更改都正确地反映在您最初尝试传递指针的强变量中。

这里的问题是你的-getCallbackToAssignTo:方法将指向指针的指针存储到一个数据结构(一个块)中,该数据结构的寿命超过了方法的执行(返回了块),这允许一些代码(块的主体)到稍后使用存储的指针尝试修改它指向的指针。但是指针指向的是在方法执行后不再应该使用的临时变量。因此修改临时变量并不反映原始变量。

更糟糕的是,它基本上是未定义的行为。编译器发现在逐个回写方案之后不再使用临时变量,并且可能允许将该变量的内存重用于其他内容。你所拥有的是一个指向编译器不希望你仍然使用的变量的悬空指针。并且通过将参数类型从“指向自动释放的指针”更改为“指向强大的指针”来解决这个问题并不是真的解决了 - 即使这样可以摆脱pass-by-writeback,这可能会解决这个直接的情况,但事实仍然存在块可以退出或存储在可以在您指向的变量范围之外使用的位置,这仍然会导致您使用悬空指针。基本上,当您存储或有一个块来捕获常规C指针时,您必须非常小心,因为该块无法确保指向的变量的生命周期(与块捕获Objective-C对象指针时不同,在这种情况下,它保留它。)

答案 1 :(得分:1)

我认为你必须对块外部新创建的对象有一个强引用。您可以使用持有者类来实现此目的,例如:

@inferface Holder: NSObject
@property (strong) id object;
@end
@implementation Holder
@end

- (CallbackType)getCallbackToAssignTo:(Holder *)inHolder {
    return ^(int a){
        inHolder.object = [[SomeClass alloc] init];
        [inHolder.object setAnInt:a];
    };
}

- (void)thisShouldWork {
    Holder *theHolder = [Holder new];
    CallbackType cb = [self getCallbackToAssignTo:theHolder];

    cb(1);
    SomeClass *directPointer = theHolder.object;
}

答案 2 :(得分:1)

问题实际上是论证的自动释放。实际上是方法签名:

- (CallbackType)getCallbackToAssignTo:(SomeClass **)indirectPointer;

实际上相当于:

- (CallbackType)getCallbackToAssignTo:(SomeClass * __autoreleasing *)indirectPointer;
  

__ autoreleasing用于表示通过引用(id *)传递的参数,并在返回时自动释放。

解决方案是将__strong指定为生命周期限定符。

- (CallbackType)getCallbackToAssignTo:(SomeClass * __strong *)indirectPointer;

更多SO参考:Double pointer as Objective-C block parameter