I have this strange crash relating to ARC auto-inserting objc_retains in my code.
I have the following two classes:
@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
At some point I instantiate a MenuItem as such:
MenuItem *item = [MenuItem alloc] initWithTarget:widget action:@selector(someMethod:) object:nil];
Then elsewhere I invoke performAction
on the menu item:
[item performAction];
In the implementation of someMethod
I get a crash:
@implementation Widget
- (void)someMethod:(id)sender
{
// EXEC_BAD_ACCESS crash in objc_retain
}
@end
Why is this happening?
The reason for the crash was because I was using the wrong
performSelector
.NSObject
defines multiple versions ofperformSelector
. The one I was invoking was:However the method I was invoking took an
id
parameter. Eg:Now ARC being the nice safe memory management system that it is tries to ensure that parameters are properly retained during the execution of a method. So even though my
someMethod:
was empty ARC was producing code that looked like this:The problem with this however was that I was invoking
performSelector:
and not supplying a value for thesender
parameter. Sosender
was pointing at random junk on the stack. Therefore whenobjc_retain()
was invoked the app crashed.If I change:
to
and
to
Then the crash goes away.
Similarly I can also change
to
if I want to follow the 'standard' form of target-action methods that take a single parameter. The benefit of the second form of
performSelector
is that if I'm invoking a method that doesn't take a parameter it will still work fine.