2016-11-17 64 views
4

我有结合的间接指针到一个块中,并返回供以后分配块成直接指针,这样的功能:目标C间接指针未能更新直接指针

@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 

对我怎样才能使这个回调的工作任何见解?

+0

不知道如何发生这种情况,但我觉得有趣的是,直接指针所在的地址与indirectPointer的地址不同(分别以60和50结尾) –

+0

假设在您的真实代码中,重新传递一个ivar的地址来设置?否则,你应该让Block直接返回新的实例。 –

回答

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

这个问题实际上是自动释放的说法。其实方法签名:

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

实际上等同于:

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

__autoreleasing用于表示由引用传递(ID *)和上回被自动释放参数。

解决方案是指定__strong作为生存期限定符。

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

更多,从而可以参照:Double pointer as Objective-C block parameter

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 *参数之间没有区别,因为通过逐回写机制确保在执行该方法期间所做的任何更改都能够正确反映在您最初尝试传递指针的强变量中。

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

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