2012-08-09 11 views
10

Mam tę dziwną awarię związaną z automatycznym wstawianiem obiektów objc_retains w moim kodzie.Crash w objc_retain w metodzie wykonywanej z performSelector

Mam dwie następujące klasy:

@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 

w pewnym momencie I instancji MenuItem jako takie:

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

Następnie indziej modlę performAction na element menu:

[item performAction]; 

W implementacji someMethod Występuje awaria:

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

Dlaczego tak się dzieje?

Odpowiedz

17

Przyczyną awarii było to, że użyłem niewłaściwego performSelector.

NSObject definiuje wiele wersji performSelector. Jednej byłem powołując się:

- (id)performSelector:(SEL)aSelector; 

Jednak metoda byłem powołując wziął parametr id. Np:

- (void)someMethod:(id)sender; 

Teraz ARC jest miły bezpieczny system zarządzania pamięcią, że jest stara, aby upewnić się, że parametry są właściwie przechowywane podczas wykonywania metody. Dlatego, mimo że mój someMethod: była pusta ARC produkował kod, który wyglądał tak:

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

Problem w tym jednak było to, że powołując performSelector: i nie dostarczając wartość parametru sender. Więc sender wskazywał losowe śmieci na stosie. Dlatego po wywołaniu objc_retain() aplikacja uległa awarii.

Jeśli zmienię:

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

do

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

i

- (void)someMethod:(id)sender; 

do

- (void)someMethod; 

A potem katastrofa odchodzi.

Podobnie mogę też zmienić

[self.target performSelector:self.action]; 

do

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

jeśli chcę podążać za „standard” formę metod docelowego działania, które mają jeden parametr.Zaletą drugiej postaci performSelector jest to, że jeśli wywołuję metodę, która nie przyjmuje parametru, nadal będzie działała poprawnie.

Powiązane problemy