在使用performSelector执行的方法中发生了objc_retain崩溃。

11 浏览
0 Comments

在使用performSelector执行的方法中发生了objc_retain崩溃。

在我的代码中,与ARC自动插入objc_retains相关的奇怪崩溃问题。我有以下两个类:\n

@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

\n在某个地方,我像这样实例化了一个MenuItem:\n

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

\n然后在其他地方,我调用了菜单项的performAction方法:\n

[item performAction];

\n在someMethod的实现中,我遇到了崩溃:\n

@implementation Widget
- (void)someMethod:(id)sender
{
    // 在objc_retain中出现EXEC_BAD_ACCESS崩溃
}
@end

\n为什么会出现这种情况?

0
0 Comments

在这个问题中,崩溃的原因是因为我使用了错误的performSelector方法。

NSObject定义了多个版本的performSelector方法。我调用的是:

- (id)performSelector:(SEL)aSelector;

然而,我调用的方法需要一个id参数,例如:

- (void)someMethod:(id)sender;

现在,由于ARC是一个安全的内存管理系统,它试图确保在方法执行过程中参数被正确地保留。因此,即使我的someMethod:是空的,ARC生成的代码看起来像这样:

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

然而,问题在于我调用了performSelector:而没有为sender参数提供值。因此,sender指向堆栈上的随机垃圾数据。因此,当调用objc_retain()时,应用程序崩溃了。

如果我将:

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

改为:

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

和:

- (void)someMethod:(id)sender;

改为:

- (void)someMethod;

那么崩溃就消失了。

同样地,如果我想遵循接受单个参数的标准target-action方法的形式,我还可以将:

[self.target performSelector:self.action];

改为:

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

第二种形式的performSelector的好处是,如果我调用的方法不需要参数,它仍然可以正常工作。

0