【问题标题】:Is it possible to determine if an Objective-C exception will be caught?是否可以确定是否会捕获 Objective-C 异常?
【发布时间】:2015-11-14 11:15:45
【问题描述】:

我在 iOS(和 OS X)中遇到了一种情况,当他们尝试解压缩损坏的存档时,来自 NSKeyedUnarchiver 的异常会导致纯二进制第三方框架崩溃我的应用程序(当部署在现场时),因此强制用户删除并重新安装应用程序。这种情况不会经常发生,但我希望这个数字为零。

我无法通过包装 NSKeyedUnarchiver 调用来解决问题,因为我没有源代码,而且这些调用不是我的代码所做的任何事情的直接结果;它们在任意时间在任意后台线程上运行。

我目前正在调整 NSKeyedUnarchiver 类,以便读取损坏的存档返回 nil(好像文件不存在)而不是抛出异常,但我不能确定是否有任何第三方框架可能正确地做事(使用@try/@catch 块),如果我这样做,可能会以有趣的方式中断。

如果我能以某种方式检查 Objective-C 异常处理树(或等效的)以确定异常处理程序是否会在抛出异常时捕获异常,以及如果是,那将是有帮助的。这样一来,如果异常会一直持续到 Crashlytics(它会重新抛出它,导致崩溃),我的修补方法可能会返回 nil,但如果其他处理程序捕获它,则可能会重新抛出异常。

这样的事情有可能吗,如果有,怎么做?

【问题讨论】:

  • 我认为真正需要解决的问题是存档损坏。
  • 当一个应用程序拥有超过一百万的活跃用户时,你肯定会在一个月内遇到至少一到两次 NSKeyedArchiver 故障,这只是随机的厄运(RAM 不稳定、CPU 故障、宇宙射线、 API 中的错误、malloc 内存池的随机损坏、有缺陷的闪存块等)。所以保证档案没有损坏是不切实际的(而且因为我正在使用闭源框架,我什至无法获得所有可能的档案的列表来预先扫描它们是否损坏)。
  • 问题不是在调试过程中捕获异常;问题是试图确保现场的真实用户不会因为某些随机广告 API 使用的一些随机数据而无法启动应用程序,这些数据对应用程序的功能并不重要。基本上,我试图尽量减少一些糟糕的第三方代码给用户带来的痛苦。

标签: ios objective-c


【解决方案1】:

为什么不将抛出异常的调用点封装在 try/catch/finally 中?

    @try {
      //call to your third party unarchiver
    }

    @catch {
      //remove your corrupted archive
    }

    @finally {
      //party
    }

滚动您自己的全局异常处理程序也可以在这里使用,ala:How do you implement global iPhone Exception Handling?

【讨论】:

  • 因为我无法控制调用 NSKeyedUnarchiver 的代码。有问题的代码位于第三方的大型二进制 blob 中。我能做的最好的事情是调整 NSKeyedUnarchiver 中的方法以防止它们抛出异常(将原始方法包装在 @try 块中),但是如果这些调用者中的任何一个期望得到异常,这仍然无济于事。
  • 另外,AFAIK,全局异常处理程序也无济于事,因为到代码执行时,堆栈已经展开,并且触发异常的代码不再执行,所以有没有办法让只有二进制的第三方代码继续执行,就好像文件不存在一样(没有做一些真正邪恶的事情)。
【解决方案2】:

如果您不确定使用 @try/@catch 包装第三方库代码是否足够好,您可以挂钩 NSKeyedUnarchiver 方法以使用完全相同的包装器替换它们,从而确保永远不会抛出异常。这是伪代码:

@try {
  //call original NSKeyedUnarchiver implementation
}
@catch {
  return nil;
}

Objc runtime has public APIs能做这种事

【讨论】:

  • 我已经在调动 NSKeyedUnarchiver 来捕捉异常并返回 nil。问题是我是否有办法检测异常处理堆栈中是否已经有一个 @try 块,这样如果其中一个调用者(我无法直接控制)实际上期望得到一个异常,我可以抛出一个而不是只返回 nil,以避免破坏二进制兼容性。
  • 为了澄清,我根本无法包装第三方库代码。我的代码不在我试图修复的崩溃的回溯中。这些库在不同的随机时间将这些操作安排在不同的运行循环上。 Swizzling实际上是解决它的唯一方法。我只是希望我可以检测到调用者是否有适当的 @try 块,以便我对方法行为的更改可以限制在不这样做会导致崩溃的情况下。
  • 您可以反汇编该库并找出其中的哪些 objc 方法会出现异常。钩住它们并以某种方式让被钩住的NSKeyedUnarchiver 方法知道它不应该抑制异常。例如,通过全局变量。
  • 除此之外没有其他简单的方法可以做到这一点。当然,您可以考虑在运行时通过检查调用堆栈中的周围代码来检测 @try 块。但这似乎很难正确地做到。您要么必须通过检查可能很容易中断的预定义字节区域来检查调用者的代码(区域可能太小,所以你什么都找不到,或者太大,你在另一个过程中找到了一些东西)。或者您需要通过分析程序集来检测典型的序言和尾声,以某种方式检测调用者函数边界。更糟糕的是,还有拇指模式
  • 我假设 Objective-C 运行时中的某些东西必须知道所有这些 try 块在哪里;毕竟不同的try块可以捕捉到不同特异性的异常,所以不能只是自动跳转到最近的目的地址;一定有某种堆栈。无论是那个还是开始一个 try 块的代码都必须有某种方法来找出下一个更高的块在哪里,以便它可以在决定不捕获特定异常类型时 longjmp 到它。无论哪种方式,似乎都应该可以连接到相同的数据,至少在理论上是这样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多