【问题标题】:Message forwarding does not work on particular messages消息转发不适用于特定消息
【发布时间】:2013-11-05 20:23:57
【问题描述】:

我发现某些 Obj-C 消息在对象未明确处理时无法正确转发。

这是一个例子:

创建一个新的 UITableView,如:

UITableView *v ? [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 0, 0) style:UITableViewStylePlain];

然后使用以下方法创建一个 NSObject 的实例:

(A) - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSLog(@"tableView:numberOfRowsInSection:");
    return 1;
}
(B) - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"tableView:cellForRowAtIndexPath:");
    return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"methodSignatureForSelector: %@", NSStringFromSelector(aSelector));
    return [NSMethodSignature signatureWithObjCTypes:"@@:@"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation: %@", NSStringFromSelector(anInvocation.selector));
}

让我们将此对象称为 ds 并将其设置为表格视图的数据源:

v.dataSource = ds;

当您运行代码时,您应该会看到方法 (A) 和 (B) 都被表格视图以预期的顺序调用:

LOG: tableView:numberOfRowsInSection:
LOG: tableView:cellForRowAtIndexPath:

现在它会变得有趣,尝试删除(或只是更改名称)方法(A),你应该得到:

LOG: methodSignatureForSelector: tableView:numberOfRowsInSection:
LOG: forwardInvocation: tableView:numberOfRowsInSection:

这很好,因为表格视图尝试调用 tableView:numberOfRowsInSection: 并且由于该方法不存在,消息被转发到 forwardInvocation:

显然,这是所有引用未在对象中定义的方法的消息的默认行为。

但是尝试恢复(A)方法并删除(B),你应该得到:

LOG: tableView:numberOfRowsInSection:

... 和一个错误告诉你 tableView:cellForRowAtIndexPath: 被调用但没有返回一个单元格。

由于某种原因,tableView:cellForRowAtIndexPath: 被调用(或未被调用)但没有遵循预期的 forwardInvocation: 路径。

这是一个错误吗?

我做错了吗?

是否存在一些明确不转发的消息?


尽管 respondsToSelector: 应该向这两个函数返回 FALSE,但在实现中的某处将 cellForRowAtIndexPath: 的消息发送到其他对象并破坏了默认的预期 行为...

我学到的基本上是:

如果您打算响应对象中的消息,即使通过 forwardInvocation: 的某些实现,您也应该始终重载 respondsToSelector: strong> 方法并为您计划在对象中实现的所有消息返回 TRUE。

【问题讨论】:

标签: ios objective-c cocoa


【解决方案1】:

以下是我基于一些实验的假设:

不同之处在于,如果未定义表视图,它不会尝试调用cellForRowAtIndexPath,而会尝试调用numberOfRowsInSection。因此,您只能看到带有numberOfRowsInSection 的转发机制。告诉您未返回单元格的错误只是措辞不当且有点误导。

我通过观察得出了这个结论,如果数据源没有定义cellForRowAtIndexPath,则会调用UITableViewController 上的实现。因此,表格视图显然正在检查是否存在。因此,按理说发生的事情是这样的:

  1. 如果已定义,请致电 dataSource
  2. 否则,请致电UITableViewController(如果已定义)
  3. 否则,不要调用任何东西

如果达到 (3),则在下游某处抛出错误,因为表格视图的内部单元格变量是 nil

【讨论】:

  • 是的,显然 UITableView 在幕后做了一些奇怪的事情,我投票给你,所以你也会得到一些代表。谢谢!
【解决方案2】:

仅当运行时系统无法在目标类或其任何祖先中找到给定选择器的匹配项时,它才会转发消息。但是,如果运行时系统对选择器的搜索成功,它会直接调用方法的实现——无需转发。

顺便说一句,如果查找失败,运行时系统首先调用forwardingTargetForSelector:;只有当forwardingTargetForSelector: 返回nilself 时,它才会调用forwardInvocation:

【讨论】:

  • 不是“本身”的答案,但绝对指出我正确的方向并解决了它,谢谢!
  • @almosnow 看起来你在我打字的时候接受了这个。我的回答还有一个有趣的观察。