如何使用Codable序列化存在引用循环的自定义类?
用Codable处理存在引用循环的自定义类
当然可以用Codable搞定有循环引用的自定义类!你遇到的EXC_BAD_ACCESS确实是循环引用导致的——Codable默认的自动合成实现会无限递归遍历引用关系,最终引发内存访问错误。下面我给你一步步拆解解决方法:
核心思路:通过唯一标识符打破递归
Codable自动合成的编码/解码逻辑会直接遍历所有属性,遇到循环引用时就会无限递归下去。我们的解决办法是:给每个实例分配一个唯一标识符,编码时只存标识符而非整个实例;解码时先把所有实例创建出来,再根据标识符关联引用。
1. 给类添加唯一标识符
首先,给你的自定义类加一个uuid属性,用来唯一标记每个实例:
import Foundation class Person: Codable { let uuid = UUID() let name: String var spouse: Person? // 强引用形成循环 init(name: String) { self.name = name } // 接下来手动实现Codable方法 }
2. 自定义编码逻辑
在encode(to:)方法里,我们不直接编码spouse实例,而是编码它的uuid(如果存在的话):
// 定义编码键 enum CodingKeys: String, CodingKey { case uuid, name, spouseUUID } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(uuid, forKey: .uuid) try container.encode(name, forKey: .name) // 只编码spouse的uuid,而非整个实例 try container.encode(spouse?.uuid, forKey: .spouseUUID) }
3. 分两步完成解码
解码的关键是先创建所有实例,再关联引用,我们需要借助Decoder的userInfo来传递一个临时字典,用来存储已解码的实例:
required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.uuid = try container.decode(UUID.self, forKey: .uuid) self.name = try container.decode(String.self, forKey: .name) // 先暂存spouse的uuid,后面再关联 let spouseUUID = try container.decodeIfPresent(UUID.self, forKey: .spouseUUID) // 从userInfo中取出临时字典,把当前实例存进去 guard let instancesDict = decoder.userInfo[CodingUserInfoKey(rawValue: "PersonInstances")!] as? NSMutableDictionary else { throw DecodingError.dataCorruptedError( forKey: .uuid, in: container, debugDescription: "No instances dictionary provided in decoder userInfo" ) } instancesDict[uuid] = self // 根据uuid从字典中找到对应的spouse实例,完成关联 if let spouseUUID = spouseUUID { self.spouse = instancesDict[spouseUUID] as? Person } }
4. 完整的编码解码流程
编码直接用JSONEncoder即可,解码时要先创建临时字典并传入JSONDecoder的userInfo:
// 创建带循环引用的实例 let alice = Person(name: "Alice") let bob = Person(name: "Bob") alice.spouse = bob bob.spouse = alice // 编码 let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let data = try encoder.encode([alice, bob]) print(String(data: data, encoding: .utf8)!) // 解码 let decoder = JSONDecoder() let instancesDict = NSMutableDictionary() // 把临时字典存入userInfo decoder.userInfo[CodingUserInfoKey(rawValue: "PersonInstances")!] = instancesDict let decodedPeople = try decoder.decode([Person].self, from: data) // 验证引用关系 print(decodedPeople[0].spouse?.name ?? "No spouse") // 输出 Bob print(decodedPeople[1].spouse?.name ?? "No spouse") // 输出 Alice
扩展说明
- 如果是多类之间的复杂循环引用,只需要给每个涉及的类都添加
uuid,解码时为每个类维护对应的实例字典,或者用一个统一的[UUID: Any]字典存储所有实例 - 要是不想手动写所有Codable逻辑,也可以用一些第三方库简化,但手动实现的方式最灵活,能完全掌控整个流程
- 注意:值类型(struct)不会有循环引用问题,只有引用类型(class)才需要处理这种场景
内容的提问来源于stack exchange,提问作者A. Vage
相关产品推荐
相关产品推荐

