Beim Lernen über NSInvocations scheint es, als ob ich eine Lücke in meinem Verständnis über Speicherverwaltung habe.NSInvocation & NSError - __Autoreleasing & Memory Crasher
Hier ein Beispielprojekt ist:
@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
Natürlich alles, was ich hier tun, ist ein Aufruf Objekt als Eigenschaft für die Methode NSString Klasse
+[NSString stringWithContentsOfFile:(NSString \*)path encoding:(NSStringEncoding)enc error:(NSError \**)error]
Es macht Sinn, den Aufbau , insbesondere nach dem Lesen von this blog post, warum ich das NSError-Objekt behandeln muss, indem ich die Adresse ** errorPointer deklariere und zuweise. Was etwas schwierig zu verstehen ist, ist das __Autoreleasing und Speichermanagement, was hier passiert.
Die ** errorPointer-Variable ist kein Objekt, daher hat sie keine Retain-Anzahl. Es ist einfach Speicher, der eine Speicheradresse speichert, die auf ein NSError-Objekt zeigt. Ich verstehe, dass die Methode stringWith ... ein NSError-Objekt zuweist, initialisiert und automatisch freigibt und den * errorPointer = den zugewiesenen Speicher einstellt. Wie Sie später sehen werden, ist das NSError-Objekt nicht mehr zugänglich. Ist das ...
- ... weil ein Autorelease-Pool leer ist?
- ... weil ARC den Aufruf "release" zu stringWith ... 's alloc + init gefüllt?
Also lassen Sie uns einen Blick darauf, wie der Aufruf „arbeitet“
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;
}
Dies war Augenöffnung (ein bisschen befremdlich) für mich, da ich dachte, dass ich wusste, was zum Teufel ich tat, als es kam zur Speicherverwaltung!
Das Beste, was ich tun könnte, um meinen Crasher zu lösen, ist, die NSError * -Fehlervariable aus der init-Methode herauszuziehen und global zu machen. Dazu musste ich das Attribut von __autoreleasing auf __strong on ** errorPointer ändern. Aber klar, diese Lösung ist weniger als ideal, zumal es wahrscheinlich ist, dass NSInvocations viele Male in einer Operationswarteschlange wiederverwendet wird. Es bestätigt auch nur irgendwie mein Verdacht, dass * Fehler wird dealloc'd.
Als letzten WTF habe ich versucht, ein bisschen mit den __bridge Casts gespielt, aber 1. Ich bin mir nicht sicher, ob das hier ist und 2. nach dem Permutieren konnte ich keinen finden, der funktionierte.
Ich würde gerne einige Einblicke, die mir helfen können, besser zu verstehen, warum das alles nicht klickt.
Gotcha! also wirklich, ich hätte den NSError * -Fehler als '__starken statischen NSError * -Fehler deklarieren sollen (macht es eine Heap-Variable? Ist das überhaupt vernünftig zu sagen? Ich dachte Stack/Heap war ein Implementierungsdetail ...) – edelaney05
Aah, nein mach es nicht statisch. Machen Sie "NSInvocation" nicht zu einem öffentlichen Teil Ihrer Klasse. Der Fehler tritt nur auf, weil Sie darauf zugreifen können, nachdem es nicht mehr nützlich ist. Wenn Sie * müssen *, machen Sie die Fehlervariable zu einer Eigenschaft Ihrer Klasse. (Übrigens ist Stack vs. Heap kein Implementierungsdetail, sondern fundamentale Konzepte für die Lebensdauer von Objekten.) –
Das Fehlerargument ist nur nützlich * nachdem der Aufruf ausgelöst wurde. Sie werden wahrscheinlich einen Aufruf an einen Zeitgeber oder eine Operationswarteschlange hängen, so dass die Fehlervariable fast sicher außerhalb des Gültigkeitsbereichs liegt. Aber der allgemeine Grund warum es abstürzt, ist jetzt kristallklar - danke! – edelaney05