Изучая 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:®ularError];
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. после перестановки я не смог найти тот, который работал.
Мне понравилось бы понимание, которое могло бы помочь мне лучше понять, почему все это не щелкает.
Gotcha! так что действительно, я должен был объявить ошибку NSError * как '__strong static NSError * error' (что делает ее переменной кучи? это даже разумно сказать? Я думал, что стек/куча была деталью реализации ...) – edelaney05
Aah, no , не делайте его статичным. Просто не делайте 'NSInvocation' общедоступной частью вашего класса. Ошибка возникает только потому, что вы можете получить к ней доступ после того, как она перестанет быть полезной. Если вы * must *, сделайте переменную ошибки свойством вашего класса. (Кстати, стек против кучи не является детальностью реализации, они являются концепциями жизненного цикла основных объектов.) –
Аргумент ошибки полезен только * после того, как * был запущен вызов. Вероятно, вы будете использовать вызов в таймере или в очереди на выполнение, поэтому переменная ошибки почти наверняка будет недоступна. Но общий смысл в том, почему он рушится, теперь кристально чистый - спасибо! – edelaney05