如果在编译时知道选择器及其参数,为什么在运行时使用performSelector:withObject:withObject?

时间:2011-02-05 00:50:00

标签: objective-c dynamic runtime llvm

我刚看到Three20中的一些代码看起来像这样:

  SEL sel = @selector(textField:didAddCellAtIndex:);
  if ([self.delegate respondsToSelector:sel]) {
    [self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];
  }

在LLVM 2.0上,这会导致编译错误:

  

错误:指向接口'id'的指针算术,在非脆弱的ABI中不是常量大小

我知道为什么会发生错误,我知道如何解决它。我只需要直接调用该方法,如下所示:

  SEL sel = @selector(textField:didAddCellAtIndex:);
  if ([self.delegate respondsToSelector:sel]) {
    [self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)];
  }

我的问题是,如果你在编译时知道选择器及其参数,为什么还需要在运行时使用performSelector:withObject:withObject:?我不明白为什么代码首先以这种方式编写。如果选择器和参数动态传递给方法,我可能会理解,但它们不是,选择器及其参数是硬编码的(即使索引在运行时确实发生了变化,它获取索引的方法也很难编码。)

如果有人能向我解释为什么有必要这样做,我将不胜感激。否则,我将在这里更改所有这些代码。

2 个答案:

答案 0 :(得分:10)

经过一番挖掘,看起来这个代码所在的TTPickerTextField类是UITextField的间接子类。

因此,它支持UITextField委托属性,该属性不符合声明方法TTPickerTextFieldDelegate的{​​{1}}协议。

我得出结论,这段代码只是懒惰。没有必要为textField:didAddCellAtIndex:委托属性提供支持,这使得这个令人困惑,容易出错的代码变得必要。

我自己的方法是单独留下UITextField委托属性,并在我处理特定委托方法的特定子类中添加我自己的属性。

只是为了澄清 - 我在问题中提到的'解决方案'修复了编译器错误,但是生成一个警告,说明找不到该方法并假定返回id。这就是原始代码“解决”但只适用于GCC。不再使用LLVM 2.0。

最后编辑,我保证:

我打击这种懒惰并摆脱警告和错误的最终解决方案是一个丑陋的黑客:

UITextField

[(id <TTPickerTextFieldDelegate>)self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)]; 委托转换为符合UITextField的{​​{1}},然后直接调用该方法。

请不要偷懒:(

答案 1 :(得分:5)

respontsToSelector / performSelector组合是可选委托方法的惯用法。委托不保证定义该方法,因此直接调用它会导致编译器警告。

在这种情况下,编译器实际抱怨的是什么:

[self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];

error: arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI

是危险的指针算术...'id'是指针类型,所以:

(id)_cellViews.count-1

告诉编译器它将从指针中减去一个而不是整数....这可能不是该代码的意图。 performSelector的withObject参数必须是指针,它不能是原语。你可以通过在NSNumber中包装_cellViews.count - 1并在委托方法中解包它来解决这个问题。

[self.delegate performSelector:sel withObject:self withObject:[NSNumber numberWithInt:_cellViews.count-1]];