NSInvocation& NSError - __autoreleasing&记忆破坏者

时间:2012-04-03 22:46:05

标签: objective-c automatic-ref-counting nsinvocation

在了解NSInvocations时,似乎我对内存管理的理解存在差距。

以下是一个示例项目:

@interface DoNothing : NSObject
@property (nonatomic, strong) NSInvocation *invocation;
@end

@implementation DoNothing
@synthesize invocation = _invocation;

NSString *path = @"/Volumes/Macintosh HD/Users/developer/Desktop/string.txt";

- (id)init
{
    self = [super init];
    if (self) {

        SEL selector = @selector(stringWithContentsOfFile:encoding:error:);
        NSInvocation *i = [NSInvocation invocationWithMethodSignature:[NSString methodSignatureForSelector:selector]];

        Class target = [NSString class];
        [i setTarget:target];
        [i setSelector:@selector(stringWithContentsOfFile:encoding:error:)];

        [i setArgument:&path atIndex:2];

        NSStringEncoding enc = NSASCIIStringEncoding;
        [i setArgument:&enc atIndex:3];

        __autoreleasing NSError *error;
        __autoreleasing NSError **errorPointer = &error;
        [i setArgument:&errorPointer atIndex:4];

        // I understand that I need to declare an *error in order to make sure
        // that **errorPointer points to valid memory. But, I am fuzzy on the
        // __autoreleasing aspect. Using __strong doesn't prevent a crasher.

        [self setInvocation:i];
    }

    return self;
}

@end

当然,我在这里所做的就是构建一个调用对象作为NSString类方法的属性

+[NSString stringWithContentsOfFile:(NSString \*)path encoding:(NSStringEncoding)enc error:(NSError \**)error]

有意义的是,特别是在阅读this blog post之后,为什么我需要通过声明并将地址分配给** errorPointer来处理NSError对象。有点难以理解的是__autoreleasing和内存管理这里发生了什么。

** errorPointer变量不是对象,因此它没有保留计数。它只是存储一个指向NSError对象的内存地址的内存。我知道stringWith ...方法将分配,初始化和自动释放NSError对象,并设置* errorPointer =分配的内存。正如您稍后将看到的,NSError对象变得不可访问。这是......

  • ...因为自动释放池耗尽了吗?
  • ...因为ARC填写了对stringWith的“释放”调用...的alloc + init?

让我们来看看调用“如何工作”

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        NSError *regularError = nil;
        NSString *aReturn = [NSString stringWithContentsOfFile:path
                                                      encoding:NSASCIIStringEncoding
                                                         error:&regularError];

        NSLog(@"%@", aReturn);

        DoNothing *thing = [[DoNothing alloc] init];
        NSInvocation *invocation = [thing invocation];

        [invocation invoke];

        __strong NSError **getErrorPointer;
        [invocation getArgument:&getErrorPointer atIndex:4];
        __strong NSError *getError = *getErrorPointer;  // CRASH! EXC_BAD_ACCESS

        // It doesn't really matter what kind of attribute I set on the NSError
        // variables; it crashes. This leads me to believe that the NSError
        // object that is pointed to is being deallocated (and inspecting with
        // NSZombies on, confirms this).

        NSString *bReturn;
        [invocation getReturnValue:&bReturn];
    }
    return 0;
}

这对我来说是开眼界(有点令人不安),因为我以为我知道在内存管理方面我到底做了什么!

我能解决崩溃问题的最好方法是从init方法中取出NSError *错误变量,并使其成为全局变量。这要求我在** errorPointer上将属性从__autoreleasing更改为__strong。但是,很明显,修复不太理想,特别是考虑到可能在操作队列中多次重用NSInvocations。它也只是有点证实了我怀疑*错误被解除了。

作为最终的WTF,我尝试使用__bridge演员,但是1.我不确定这是否是我在这里所需要的2.在置换之后我找不到一个有用的。

我喜欢一些可以帮助我更好地理解为什么这一切都没有点击的洞察力。

1 个答案:

答案 0 :(得分:6)

这实际上是一个非常简单的错误,与自动引用计数无关。

-[DoNothing init]中,您正在使用指向堆栈变量的指针初始化调用的error参数:

__autoreleasing NSError *error;
__autoreleasing NSError **errorPointer = &error;
[i setArgument:&errorPointer atIndex:4];

main中,你抓住同一个指针并取消引用它:

__strong NSError **getErrorPointer;
[invocation getArgument:&getErrorPointer atIndex:4];
__strong NSError *getError = *getErrorPointer;

但当然,到目前为止,-[DoNothing init]中存在的所有局部变量都不再存在,并且尝试从一个变量读取会导致崩溃。