NSKeyedUnarchiver - 需要尝试/捕获?

Posted

技术标签:

【中文标题】NSKeyedUnarchiver - 需要尝试/捕获?【英文标题】:NSKeyedUnarchiver - try/catch needed? 【发布时间】:2013-06-22 08:55:27 【问题描述】:

据我了解,不鼓励使用 @try/@catch 块,因为 exceptions 只应在不可恢复的灾难性错误中抛出(请参阅此讨论,@bbum 给出了很好的答案:Exception Handeling in ios)。

所以我查看了我的代码,发现了一个我不知道如何摆脱的 @try/@catch 块:

NSData *fileData = [NSData dataWithContentsOfFile: ....];

NSDictionary *dictionary;

@try 
   dictionary = [NSKeyedUnarchiver unarchiveObjectWithData: fileData];

@catch (NSException *exception) 
   //....

@finally 
  //...

问题是(如documentation 中所述)+unarchiveObjectWithData: 如果NSData 不包含有效存档,则会引发NSInvalidArchiveOperationException

由于数据是由用户选择的文件提供的,因此不能保证它包含有效的存档,因此如果选择了不正确的文件,应用程序将会崩溃。

现在有两个问题:

    如果存档无效,为什么+unarchiveObjectWithData: 不只返回nil编辑:NSError**)(这似乎不符合灾难性或不可恢复的错误) . 上面的模式是否正确(使用@try)?我没有发现任何方法可以让我们事先检查数据是否包含有效的存档,并且没有发现使用委托协议处理这种情况的可能性。我忽略了什么?

请注意,上面的代码当然有效,我只是想知道它是否是最佳实践。

【问题讨论】:

@Ramy Al Zuhouri:我没有把字典写成.plist。我使用NSKeyedArchiver 将它归档到NSData 对象中,并将NSData 写入文件(我不想像plist 那样为应用程序的用户存储易于阅读和修改的文件,这就是为什么我首先使用了NSData 我想解决这个问题的唯一方法是使用 encodeObject:forKey:decodeObjectForKey: 保存/加载。 @RamyAlZuhouri 好主意。然而,当反序​​列化损坏的数据时,这些方法也会引发 NSRangeExceptionNSInvalidUnarchiveOperationException 等异常。 【参考方案1】:

在 iOS 9 中向NSKeyedUnarchiver 添加了一个新方法,该方法现在返回错误:

斯威夫特:

public class func unarchiveTopLevelObjectWithData(data: NSData) throws -> AnyObject?

目标-C:

+ (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error;

但是,这与以前的 iOS 版本不向后兼容,因此您需要check for framework availability。

【讨论】:

【参考方案2】:

NSKeyedArchiver 由 Apple 构建。它们控制unarchiveObjectWithData: 执行时执行的代码,因此它们还控制异常处理期间的资源管理(这是Objective-C 中异常的根源)。

如果他们可以保证在您调用 unarchiveObjectWithData: 和他们引发异常的代码点之间没有外来代码(无论是第三方代码还是您的应用程序的代码),那么理论上只要调用代码负责正确清理,就可以安全地使用异常。

问题在于,这种假设可能并非如此:通常使用NSKeyedArchiver 来序列化自定义对象。通常自定义类实现initWithCoder:来读取类数据(通过使用归档器的方法,如decodeObjectForKey:)。

如果存档器在其中一种方法中引发异常,则无法修复存档器的资源处理。异常将通过自定义对象的initWithCoder: 抛出。存档器不知道是否有比反序列化对象更多的东西要清理。所以在这种情况下,异常的发生意味着进程处于危险状态,可能会导致不需要的行为。

关于您的问题:

为什么 [NSKeyedArchiver 不使用适当的 Cocoa 错误处理]?

只有构建存档器的 Apple 工程师知道。我的猜测是异常处理和键控归档几乎是同时构建的(大约在 2001 年),那时还不清楚异常处理永远不会成为 Objective-C 中的一等公民。

@try 模式是否正确?

由于上述注意事项的限制,它是正确的。 如果Apple 的代码正确处理异常情况并且您自己的序列化代码执行相同的@try 模式可能是正确的。

但是,要完全正确是非常困难的。您必须确保所有执行的代码都知道异常并正确清理。

例如,ARC,does no exception cleanup 默认用于局部变量和临时对象(您必须启用 -fobjc-arc-exceptions 才能执行此操作)。

此外,没有关于 @synthesized 属性的访问器的异常安全性的文档(当 atomic 时,它们可能会泄漏锁)。

结论:

有无数种微妙的方式可以说明异常如何破坏内容。在 Objective-C 中构建异常安全代码是困难的,并且需要深入了解所有相关部分的实现。

所有这些都导致了结论。如果您想在加载可能损坏的档案时优雅地处理错误并在之后继续正常执行:不要使用NSKeyedArchiver

【讨论】:

当然,ARC 和正在使用的新的基于 C++ 的异常 ABI 确实有助于缓解这个问题,但遗留代码肯定仍然存在问题。 @RichardJ.RossIII 我不同意。如前所述,有许多可想而知的方式可能会破坏代码。 ARC 只能帮助清理内存,但这只是问题的一部分。如果您想构建安全且正确的代码,要么根本不使用 Objective-C 中的任何异常(或者至少不要在连接不同 API 时使用它们)。

以上是关于NSKeyedUnarchiver - 需要尝试/捕获?的主要内容,如果未能解决你的问题,请参考以下文章

NSKeyedUnArchiver 崩溃

NSKeyedUnarchiver.unarchiveTopLevelObjectWithData 在 Swift 4 中已过时

使用 NSKeyedUnarchiver 解码 URL 对象数组

Swift 防止 NSKeyedUnarchiver.decodeObject 崩溃的唯一方法?

Swift iOS ARKit 从文件 NSKeyedUnarchiver NSKeyedArchiver 加载 ARWorldMap 数据

错误处理 - NSKeyedUnarchiver