2012-08-09 12 views
10

Tengo este extraño bloqueo relacionado con la inserción automática de objc_retains de ARC en mi código.Bloqueo en objc_retain en el método realizado con performSelector

tengo las dos clases siguientes:

@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 

En algún momento me instancias de un Menultem como tal:

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

Entonces otra parte invoco performAction en el elemento de menú:

[item performAction]; 

En la implementación de someMethod tengo un bloqueo:

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

¿Por qué sucede esto?

Respuesta

17

El motivo del bloqueo fue porque estaba usando el performSelector incorrecto.

NSObject define múltiples versiones de performSelector. El que yo estaba invocando era:

- (id)performSelector:(SEL)aSelector; 

Sin embargo, el método que se estaba invocando un parámetro id. Por ejemplo:

- (void)someMethod:(id)sender; 

Ahora es el buen sistema de gestión de memoria seguro que se trata de intentos para asegurar que los parámetros se almacenen durante la ejecución de un método ARC. Así que, aunque mi someMethod: fue ARC vacío producía código que se veía así:

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

El problema con esto, sin embargo era que yo estaba invocando performSelector: y no suministrar un valor para el parámetro sender. Así que sender apuntaba a basura al azar en la pila. Por lo tanto, cuando se invocó objc_retain(), la aplicación se bloqueó.

Si cambio:

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

a

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

y

- (void)someMethod:(id)sender; 

a

- (void)someMethod; 

Entonces el choque desaparece.

mismo modo que también puede cambiar

[self.target performSelector:self.action]; 

a

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

si quiero seguir la forma 'estándar' de métodos objetivo de acción que toman un único parámetro.El beneficio de la segunda forma de performSelector es que si invoco un método que no toma un parámetro, seguirá funcionando bien.

Cuestiones relacionadas