2012-04-03 6 views
4

Изучая NSInvocations, похоже, у меня есть пробел в моем понимании управления памятью.NSInvocation & NSError - __autoreleasing & crasher памяти

Вот пример проекта:

@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, о том, почему мне нужно обработать объект NSError, объявив и присвоив адрес ** errorPointer. То, что трудно понять, - это управление __autoreleasing и память, что здесь происходит.

** errorPointer переменная не является объектом, поэтому она не имеет счет сохранения. Это просто память, которая хранит адрес памяти, который указывает на объект NSError. Я понимаю, что метод stringWith ... будет выделять, init и autorelease объект NSError и устанавливать * errorPointer = выделенную память. Как вы увидите позже, объект NSError становится недоступным. Это ...

  • ... потому что пул автореферата осушен?
  • ... потому что ARC заполняет вызов «release» на stringWith ... 's 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; 
} 

Это было открытие глаз (немного обескураживает) для меня, так как я думал, что я знал, что, черт возьми, я делал, когда он пришел к управлению памятью!

Лучшее, что я мог сделать, чтобы разрешить мой crasher, - вытащить переменную ошибки NSError * из метода init и сделать ее глобальной. Это потребовало от меня изменить атрибут от __autoreleasing до __strong на ** errorPointer. Но, очевидно, что исправление не является идеальным, особенно если учесть, что он может многократно использовать NSInvocations в очереди операций. Это также только kinda подтверждает мое подозрение, что * ошибка dealloc'd.

В качестве окончательного WTF я попытался немного поиграть с трансляциями __bridge, но 1. Я не уверен, что это то, что мне нужно здесь, и 2. после перестановки я не смог найти тот, который работал.

Мне понравилось бы понимание, которое могло бы помочь мне лучше понять, почему все это не щелкает.

ответ

6

Это на самом деле очень простая ошибка, которая не имеет ничего общего с автоматическим подсчетом ссылок.

В -[DoNothing init], вы инициализации параметра ошибки в вызове с указателем на переменную стека:

__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], больше не существуют, и попытка чтения с одного вызывает сбой.

+0

Gotcha! так что действительно, я должен был объявить ошибку NSError * как '__strong static NSError * error' (что делает ее переменной кучи? это даже разумно сказать? Я думал, что стек/куча была деталью реализации ...) – edelaney05

+0

Aah, no , не делайте его статичным. Просто не делайте 'NSInvocation' общедоступной частью вашего класса. Ошибка возникает только потому, что вы можете получить к ней доступ после того, как она перестанет быть полезной. Если вы * must *, сделайте переменную ошибки свойством вашего класса. (Кстати, стек против кучи не является детальностью реализации, они являются концепциями жизненного цикла основных объектов.) –

+0

Аргумент ошибки полезен только * после того, как * был запущен вызов. Вероятно, вы будете использовать вызов в таймере или в очереди на выполнение, поэтому переменная ошибки почти наверняка будет недоступна. Но общий смысл в том, почему он рушится, теперь кристально чистый - спасибо! – edelaney05

 Смежные вопросы

  • Нет связанных вопросов^_^