10

我在我的代码中遇到了与 ARC 自动插入 objc_retains 相关的奇怪崩溃。

我有以下两个课程:

@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

在某些时候,我这样实例化一个 MenuItem:

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

performAction然后在其他地方我在菜单项上调用:

 [item performAction];

在执行中someMethod我遇到了崩溃:

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

为什么会这样?

4

1 回答 1

18

崩溃的原因是因为我使用了错误的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:@selector(someMethod:) 
                                          object:nil];

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

- (void)someMethod:(id)sender;

- (void)someMethod;

然后崩溃消失。

同样我也可以改变

[self.target performSelector:self.action];

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

如果我想遵循采用单个参数的目标操作方法的“标准”形式。第二种形式的好处performSelector是,如果我调用一个不带参数的方法,它仍然可以正常工作。

于 2012-08-09T02:28:54.740 回答