Swift含递归指针的自定义对象编解码问题咨询
处理NSCoding中的循环引用与对象引用保留问题
你遇到的是NSCoding处理循环引用时的典型问题:当对象之间互相引用(A包含B,B包含A),普通的解码逻辑会创建重复的对象实例,丢失原有的引用关系。这背后的原因是NSCoder的引用跟踪机制需要对象完成初始化后,才能将其与编码时的唯一标识关联起来,而你的解码顺序刚好破坏了这个流程。
问题根源分析
你的init?(coder:)是一个便利初始化器,先解码了otherObjects数组,再调用self.init(name:)完成对象初始化。这会导致:
- 解码A时,先解码它的
otherObjects(包含B); - 解码B时,又解码它的
otherObjects(包含A); - 此时A还没完成初始化,NSCoder还没把A加入已解码对象的映射表,所以会新建一个A的副本;
- 最终A和B的引用指向的是不同的实例,而非原有的互相引用关系。
解决方案:调整解码顺序,先完成对象初始化
NSCoder本身支持对象引用跟踪(编码时会给每个对象分配唯一ID,重复引用只存储ID),只要我们调整解码顺序,让对象先完成初始化,再处理引用类型的属性,就能保留原有的引用关系。
修改后的代码如下:
import Foundation // Swift中使用NSCoding必须继承NSObject class MyObject : NSObject, NSCoding { var name: String var otherObjects: [MyObject] // 指定初始化器,确保对象能被完全初始化 init(name: String) { self.name = name self.otherObjects = [] } // 可选的便捷初始化器(如果需要) convenience init?(name: String?) { guard let name = name else { return nil } self.init(name: name) } func encode(with aCoder: NSCoder) { aCoder.encode(name, forKey: "name") // encodeObject会自动处理对象引用,避免重复编码 aCoder.encode(otherObjects, forKey: "others") } required init?(coder aDecoder: NSCoder) { // 第一步:先解码基本类型属性(非引用类型) guard let name = aDecoder.decodeObject(forKey: "name") as? String else { return nil } self.name = name // 第二步:初始化引用类型属性为空,完成对象的基本初始化 self.otherObjects = [] // 第三步:此时对象已完全初始化,NSCoder已将其与编码ID关联,再解码引用属性 if let others = aDecoder.decodeObject(forKey: "others") as? [MyObject] { self.otherObjects = others } } }
关键修改点说明
- 继承NSObject:Swift类必须继承NSObject才能兼容NSCoding和NSKeyedArchiver/Unarchiver的引用跟踪机制;
- 调整解码顺序:先初始化对象的基本属性,再解码引用类型的
otherObjects,确保对象在处理引用前已被NSCoder注册; - 安全解码:用可选绑定替代强制解包(
as?而非as!),避免解码失败导致崩溃; - 保留encode逻辑:
encode(with:)中的encodeObject会自动处理循环引用,无需额外修改——它会给每个对象分配唯一ID,重复引用只存储ID,不会无限递归编码。
验证引用关系
你可以用以下代码验证修改后的效果:
// 创建循环引用的对象 let a = MyObject(name: "A") let b = MyObject(name: "B") a.otherObjects.append(b) b.otherObjects.append(a) // 编码 let data = NSKeyedArchiver.archivedData(withRootObject: a) // 解码 if let decodedA = NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject { guard let decodedB = decodedA.otherObjects.first else { return } // 验证引用是否指向同一个实例(=== 检查对象身份) print(decodedA === decodedB.otherObjects.first!) // 应该输出true }
如果输出true,说明解码后的A和B仍然保持了原有的互相引用关系,没有创建副本。
内容的提问来源于stack exchange,提问作者sjg1729
相关产品推荐
相关产品推荐

