通过 NSKeyedArchiver 归档数据时崩溃

Crash while archiving data by NSKeyedArchiver

我的应用程序使用以下函数序列化一组自定义对象(仅显示相关代码):

class func serializeShoppingLocations(buyLocations: Set<ShoppingLocation>) -> Data {
    #if os(iOS)
        NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListIOS.ShoppingLocation.self)
    #elseif os(watchOS)
        NSKeyedArchiver.setClassName("ShoppingLocation", for: ShoppingListWatch.ShoppingLocation.self)
    #endif  
    let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)
    return data
}  

iOS 和 watchOS 目标的条件编译是必需的,以便允许两个目标稍后对序列化数据进行归档,即使它们已被另一个目标归档。

ShoppingLocation采用NSCoding协议,继承自NSObject

此代码在使用 EXC_BAD_ACCESS (code=1, address=0x0)let data = … 指令处崩溃。

我知道这个错误可能表示引用了一个不存在的对象。但是,变量 buyLocations 是存在的,我可以在调试器中打印该值,包括集合中对象的所有值。

堆栈跟踪是:

堆栈跟踪表明存档程序开始存档 NSSet,并尝试存档集合中的一个元素,但内部可变字典中的 setObjectForKey 失败。

可能是什么原因?

编辑:

在进一步的测试中,我发现崩溃总是在至少有 2 个应用程序线程处于活动状态时发生。
特别是,我发现了一种情况,其中一个线程在与上述指令不同的指令处崩溃(Xcode 中的红色):

let dataBlob = NSKeyedArchiver.archivedData(withRootObject: shoppingItem.buyLocations)  

另一个线程在上述指令处停止(Xcode 中的绿色):

let data = NSKeyedArchiver.archivedData(withRootObject: buyLocations)

可能是 NSKeyedArchiver 不是线程安全的?

我的问题果然是多线程访问不同步造成的:

NSArchiver 遍历对象图,从根对象开始,沿途归档它遇到的每个对象。
为了创建正确的存档,因此要求对象图在存档器终止之前不发生变化。

在单线程应用程序中,这是自动保证的。
在超过 1 个线程可以改变对象图的多线程应用程序中,程序员有责任确保这一点,因为存档器无法阻止其他线程改变根对象。 (例如,如果存档器首先制作对象图的深拷贝,然后存档副本,则对象图可能在深拷贝期间发生变异)。

因此需要序列化对对象图的所有获取和设置访问,例如通过在串行访问队列中执行它们。