通过 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 个线程可以改变对象图的多线程应用程序中,程序员有责任确保这一点,因为存档器无法阻止其他线程改变根对象。 (例如,如果存档器首先制作对象图的深拷贝,然后存档副本,则对象图可能在深拷贝期间发生变异)。
因此需要序列化对对象图的所有获取和设置访问,例如通过在串行访问队列中执行它们。
我的应用程序使用以下函数序列化一组自定义对象(仅显示相关代码):
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 个线程可以改变对象图的多线程应用程序中,程序员有责任确保这一点,因为存档器无法阻止其他线程改变根对象。 (例如,如果存档器首先制作对象图的深拷贝,然后存档副本,则对象图可能在深拷贝期间发生变异)。
因此需要序列化对对象图的所有获取和设置访问,例如通过在串行访问队列中执行它们。