You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CoreData自关联实体编码崩溃,报错EXC_BAD_ACCESS求助

解决CoreData自关联实体Codable编码循环引用崩溃问题

崩溃根源是自关联实体的双向引用导致编码时无限递归:当编码Folder的folders属性时,每个子Folder会触发自身的encode(to:)方法,而子Folder的parentFolder又指向当前Folder,如此循环往复,最终引发栈溢出或内存访问错误(EXC_BAD_ACCESS)。

解决方案:编码忽略反向关联,解码手动绑定关系

因为JSON结构中不需要存储反向关联的parentFolder(层级关系已经通过父文件夹包含子文件夹的结构隐含),所以可以调整编码和解码逻辑:

  1. 修改编码方法,跳过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)
    }
    
  2. 调整解码方法,手动设置子文件夹的父关联
    解码子文件夹后,主动将它们的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 09:25:24