2012-08-09 14 views
10

Ich habe diesen seltsamen Absturz in Bezug auf ARC automatisches Einfügen von objc_retains in meinem Code.Absturz in objc_retain in Methode durchgeführt mit performSelector

Ich habe die folgenden zwei Klassen:

@interface MenuItem : NSObject 
@property (weak, nonatomic) id target; 
@property (unsafe_unretained, nonatomic) SEL action; 
@property (strong, nonatomic) id object; 
- (instancetype)initWIthTarget:(id)target action:(SEL)action withObject:(id)object; 
- (void)performAction; 
@end 

@implementation MenuItem 
- (void)performAction 
{ 
    if (self.target && self.action) 
    { 
     if (self.object) 
     { 
     [self.target performSelector:self.action withObject:self.object]; 
     } 
     else 
     { 
     [self.target performSelector:self.action]; 
     } 
    } 
} 
@end 

@interface Widget : NSObject 
- (void)someMethod:(id)sender; 
@end 

Irgendwann habe ich eine MenuItem als solche instanziiert:

MenuItem *item = [MenuItem alloc] initWithTarget:widget action:@selector(someMethod:) object:nil]; 

Dann habe ich an anderer Stelle performAction auf den Menüpunkt aufrufen:

[item performAction]; 

In der Implementierung von someMethod bekomme ich einen Absturz:

@implementation Widget 
- (void)someMethod:(id)sender 
{ 
    // EXEC_BAD_ACCESS crash in objc_retain 
} 
@end 

Warum passiert das?

Antwort

17

Der Grund für den Absturz war, weil ich den falschen performSelector verwendet habe.

NSObject definiert mehrere Versionen von performSelector. Der, den ich wurde Berufung war:

- (id)performSelector:(SEL)aSelector; 

jedoch die Methode, die ich ein id Parameter nahm wurde aufgerufen wird. ZB:

- (void)someMethod:(id)sender; 

Jetzt ARC das schöne, sichere Speicher-Management-System ist, dass es versucht, um sicherzustellen, dass die Parameter richtig während der Ausführung eines Verfahrens zurückgehalten werden. Also auch wenn meine someMethod: war leer ARC-Code wurde produziert, die wie folgt aussah:

- (void)someMethod:(id)sender 
{ 
    objc_retain(sender); 
    objc_release(sender); 
} 

Das Problem dabei war jedoch, dass ich performSelector: wurde Berufung und nicht einen Wert für den sender Parameter liefert. So zeigte sender auf zufälligen Müll auf dem Stapel. Daher, wenn objc_retain() aufgerufen wurde, stürzte die App ab.

Wenn ich ändern:

MenuItem *item = [[MenuItem alloc] initWithTarget:widget 
              action:@selector(someMethod:) 
              object:nil]; 

zu

MenuItem *item = [[MenuItem alloc] initWithTarget:widget 
              action:@selector(someMethod) 
              object:nil]; 

und

- (void)someMethod:(id)sender; 

zu

- (void)someMethod; 

Dann geht der Absturz weg.

Ebenso kann ich auch

[self.target performSelector:self.action]; 

zu

[self.target performSelector:self.action withObject:nil]; 

wenn ich den 'Standard' Form von zielAktionsMethoden folgen ändern möchten, die einen einzelnen Parameter nehmen.Der Vorteil der zweiten Form von performSelector ist, dass wenn ich eine Methode anrufe, die keinen Parameter akzeptiert, wird es immer noch gut funktionieren.