【问题标题】:Under what conditions might instancesRespondToSelector: return true, but performSelector: throw an exception在什么情况下instancesRespondToSelector: 返回true,但是performSelector: 抛出异常
【发布时间】:2013-04-09 07:34:16
【问题描述】:

我的代码分布在一个如下所示的库中:

if ([[NSString class] instancesRespondToSelector: @selector(JSONValue)]) {
  NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
  dict = [jsonString performSelector: @selector(JSONValue)];
}

由于某种原因,在调用 performSelector: 方法时会引发 -[__NSCFString JSONValue]: unrecognized selector sent to instance 异常。这是分布在我编写的库中的代码,但我自己无法重现或调试它。相反,第三方正在报告此问题。在什么情况下instancesRespondToSelector: 在实际使用performSelector: 调用方法时会抛出异常?

编辑 有一个案例可以解释为什么会发生这种情况,但这没有意义。如果开发人员要做这样的事情:

@implementation NSString (OurHappyCategory)

+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
  return YES;
}

@end

它会解释为什么代码正在执行,但这当然是非常糟糕的事情。有没有一种合理的方式可以解决这个问题?

【问题讨论】:

  • 您确定您的“JSONValue”方法会返回字典吗?也许这是你的问题。
  • @Maggie 不管方法返回什么,异常都是由调用方法引起的。 JSONValue 方法的返回类型是id
  • 您确定第三方正在使用此代码,并带有if 子句吗?
  • @MarceloFabri 这段代码在我的静态库中,他们包含在他们的应用程序中。我知道异常在那里被抛出,因为我的代码捕获了异常并写了一条特定的消息。
  • 您确定这是对JSONValue 的唯一可能调用吗?也许第 3 方没有正确链接您的库并自己调用 JSONValue

标签: ios objective-c exception unrecognized-selector


【解决方案1】:

NSString 基本上是一个class cluster,并带来了各种复杂性......你实际上需要询问实例是否响应选择器。

NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
if ([jsonString respondsToSelector: @selector(JSONValue)]) {
  dict = [jsonString performSelector: @selector(JSONValue)];
}

但您的问题可能与编译或链接扩展有关...如果该类别已添加到库中,那么您需要添加 -ObjC 链接器标志。

编辑:
我一直在努力重现这个问题......我无法...... 您还有更多信息吗?例如,故障仅发生在模拟器上,还是仅发生在设备上、iOS 4.x、GNU 链接器与 LLDB 的链接器、ABI/运行时差异?

【讨论】:

  • 我同意这听起来可能与集群有关,但我想不出任何实际的方式这可能是一个问题。你有一个具体的例子来说明你如何最终得到所描述的结果吗?我假设 OP 试图避免创建字符串,如果不能是 JSONValue 的话。 (例如,他有一个涉及直接解析数据的后备)
  • 如果类别在 NSString 上并且 NSCFString 实际上是 NSString 的超类
  • __NSCFString 是 NSString 的子类(实际上是 NSMutableString),所以我仍然不明白它是如何导致问题的。
  • 我猜这是静态库和类别与 performSelector 之间的交互。当 performSelector 寻找 NSString 时,它似乎有可能找到绑定到没有附加类别的静态库的版本。有点像在 Java 中的两个类加载器下加载的同一个类。直接调用有效,因为实例指针始终链接到类的“正确”版本。
  • @HotLicks ABI/Runtime 不能那样工作......如果我们有编译版本,我们可以用nm 查看目标文件,看看符号是否在全部,如果它在未定义部分中。
【解决方案2】:

我猜你没有以正确的方式导入第三方库。通常这些方法作为类别添加到 NSString,我碰巧可以看到 .h 文件但 .m 没有编译。您可以在 xcode 目标->构建阶段->编译源中检查它。或者检查你是否在 Project-->Build Settings-->Other linker flag = -all_load

中添加了这个标志

【讨论】:

  • -all_load 不再需要,-ObjC 就足够了。这也不能解释为什么instancesRespondToSelector 返回true。
  • 在这种情况下,我们谈论的是使用我的库的第三方应用程序,因此我无权访问构建设置。
  • 问他们是否可以检查或给你整个项目,我在一个新项目中集成 MKNetworkKit 时遇到了几乎相同的问题。我做了很多次,但昨天它根本不起作用,我可以发送消息但没有实现该库的类别。添加 -all-load 标志一切正常。为了完整起见,我没有发送 respondToSelector 消息,但自动完成和链接器构建良好。
  • 我想知道该类别的 .m 是否不包括在内,但我看不出instancesRespondToSelector 将如何返回YES。并且大概实例..调用和 JSONValue 调用在同一个编译单元中,并且将绑定到相同的定义。
  • 那么-all_load 是否不再需要取决于他们运行的Xcode 版本?我听说有些人使用疯狂的旧版本 Xcode。
【解决方案3】:

异常并非直接来自 Objective-C 运行时以响应发送实例无法识别的消息。它来自-[NSObject doesNotRecognizeSelector:],在各种方法查找和转发机制结束时调用。

但是,任何东西都可以在需要时调用-doesNotRecognizeSelector:。一个类可以“拒绝”一个继承的方法,方法是覆盖它并使覆盖只调用-doesNotRecognizeSelector:。作为documented,这将导致您看到的异常,尽管-respondsToSelector:(和+instancesRespondToSelector:)返回YES

我无法告诉您为什么 __NSCFString 在您的最终用户中这样做。使用您的库的应用程序是否使用类别或方法调配来修改该类的方法?

另外,您的日志记录是否显示异常中的实际堆栈跟踪捕获?这可能会提供信息。

【讨论】:

    【解决方案4】:

    有一种奇怪的可能性:performSelector 本身可能以某种方式在与其余代码不同的链接加载环境中执行。不过,不完全(或什至大致)确定这是如何发生的。

    【讨论】:

    • 您能否澄清一下“链接加载环境”的含义?
    • @ThomasW - 绑定的结果,可执行模块构建,无论你想怎么称呼它。每个平台的术语都不同,我跟不上它们。特别是,如果某些部分位于单独的库模块中,则它们可能无法“看到”另一个模块中的所有内容,反之亦然。
    • 无论如何,如果这是问题,那么将 jsonString 转换为定义 JSONValue 的虚拟接口类型并直接执行调用(或直接像 Objective-C 执行任何调用一样)可能会起作用。
    • 我看不出转换 jsonString 会如何改变它响应的方法列表。
    • @JesseRusak - 我猜你看不到很多东西。强制转换不会改变字符串响应的内容,但会允许编译器“吞下”它,从而避免使用 performSelector(这可能是这个谜团中的嫌疑人)。
    【解决方案5】:

    确保没有人在混用函数

    【讨论】:

      猜你喜欢
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-12
      • 2016-10-23
      相关资源
      最近更新 更多