CoreData自关联实体编码崩溃,报错EXC_BAD_ACCESS求助
解决CoreData自关联实体Codable编码循环引用崩溃问题
崩溃根源是自关联实体的双向引用导致编码时无限递归:当编码Folder的folders属性时,每个子Folder会触发自身的encode(to:)方法,而子Folder的parentFolder又指向当前Folder,如此循环往复,最终引发栈溢出或内存访问错误(EXC_BAD_ACCESS)。
解决方案:编码忽略反向关联,解码手动绑定关系
因为JSON结构中不需要存储反向关联的parentFolder(层级关系已经通过父文件夹包含子文件夹的结构隐含),所以可以调整编码和解码逻辑:
修改编码方法,跳过
parentFolder的编码
去掉parentFolder的编码逻辑,切断循环链:func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(folders as! Set<Folder>, forKey: .folders) // 注释掉parentFolder的编码,避免循环递归 // try container.encode(parentFolder, forKey: .parentFolder) }调整解码方法,手动设置子文件夹的父关联
解码子文件夹后,主动将它们的parentFolder绑定到当前Folder实例,而不是从JSON中解码该属性:required convenience init(from decoder: Decoder) throws { guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else { throw DecoderConfigurationError.missingManagedObjectContext } self.init(context: context) let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(UUID.self, forKey: .id) self.name = try container.decode(String.self, forKey: .name) // 解码子文件夹集合 let childFolders = try container.decode(Set<Folder>.self, forKey: .folders) self.folders = childFolders as NSSet // 手动绑定子文件夹的父关联 childFolders.forEach { $0.parentFolder = self } // 不再解码parentFolder,由父文件夹层级自动设置 // self.parentFolder = try container.decodeIfPresent(Folder.self, forKey: .parentFolder) }
备选方案:跟踪已编码对象避免递归
如果必须保留parentFolder的编码,可以通过编码器的userInfo跟踪已编码的对象,重复遇到时跳过编码:
func encode(to encoder: Encoder) throws { // 定义标识已编码对象的UserInfoKey let encodedObjectsKey = CodingUserInfoKey(rawValue: "encodedObjects")! // 获取或初始化已编码对象集合 guard let encodedObjects = encoder.userInfo[encodedObjectsKey] as? NSMutableSet else { // 首次编码时初始化集合,传递给子编码器 let newEncodedObjects = NSMutableSet() newEncodedObjects.add(self) var modifiedEncoder = encoder modifiedEncoder.userInfo[encodedObjectsKey] = newEncodedObjects try self.encode(to: modifiedEncoder) return } // 如果当前对象已编码过,直接返回,避免循环 if encodedObjects.contains(self) { return } encodedObjects.add(self) var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) try container.encode(name, forKey: .name) try container.encode(folders as! Set<Folder>, forKey: .folders) try container.encode(parentFolder, forKey: .parentFolder) }
使用时需要初始化编码器的userInfo:
let encoder = JSONEncoder() encoder.userInfo[CodingUserInfoKey(rawValue: "encodedObjects")!] = NSMutableSet()
总结
第一种方案更简洁且符合JSON数据结构的设计逻辑(层级关系无需重复存储反向引用),是解决这类循环引用编码崩溃的首选方案。
内容的提问来源于stack exchange,提问作者Francesco Leoni
相关产品推荐
相关产品推荐

