【问题标题】:Objective C indirect pointer bound to block fails to update direct pointer绑定到块的Objective C间接指针无法更新直接指针
【发布时间】:2016-11-17 17:44:01
【问题描述】:

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

@interface SomeClass : NSObject
@property int anInt;
@end
@implementation SomeClass
@end

typedef void(^CallbackType)(int a);

- (CallbackType)getCallbackToAssignTo:(SomeClass **)indirectPointer {
  return ^(int a){
    NSLog(@"indirectPointer is: %p", indirectPointer);
    NSLog(@"*indirectPointer is: %p", *indirectPointer);
    (*indirectPointer) = [[SomeClass alloc] init];
    (*indirectPointer).anInt = a;
    NSLog(@"After: indirectPointer is: %p", indirectPointer);
    NSLog(@"After: *indirectPointer is: %p", *indirectPointer);
  };
}

- (void)iWillNotDoWhatImSupposedTo {
  SomeClass *directPointer = nil;
  CallbackType cb = [self getCallbackToAssignTo:(&directPointer)];
  NSLog(@"directPointer is pointing to: %p", directPointer);
  NSLog(@"&directPointer is pointing to: %p", &directPointer);
  cb(1);
  NSLog(@"after callback directPointer is: %p", directPointer);
  NSLog(@"after callback &directPointer is: %p", &directPointer);
}

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

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

关于如何使此回调起作用的任何见解?

【问题讨论】:

  • 不确定这是怎么发生的,但我觉得有趣的是直接指针所在的地址与indirectPointer的地址不同(分别以60和50结尾)
  • 大概在您的真实代码中,您正在传递要设置的 ivar 的地址?否则,您应该让 Block 直接返回新实例。

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


【解决方案1】:

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

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

基本上,它所做的是创建一个“自动释放”类型的临时变量,将强指针分配给它(如果值不为空),然后使用指向该临时变量的指针调用方法。然后在方法返回后,它将变量的值分配回你的强变量;大概是这样的:

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

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

这里的问题是您的 -getCallbackToAssignTo: 方法将指向指针的指针存储到数据结构(块)中,该数据结构(块)比方法的执行(返回块)更长,这允许稍后使用一些代码(块的主体)存储的指针尝试修改它指向的指针。但是指针指向的是在方法执行后不再应该使用的临时变量。所以修改临时变量并不反映在原来的变量上。

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

【讨论】:

    【解决方案2】:

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

    @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;
    }
    

    【讨论】:

      【解决方案3】:

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

      - (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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-02-10
        • 1970-01-01
        • 2019-04-10
        • 2012-02-08
        • 2011-05-19
        • 2013-07-13
        • 1970-01-01
        相关资源
        最近更新 更多