CoreData批量导入CSV数据时设置ToOne关系出现EXC_BAD_ACCESS错误如何解决
问题原因
核心原因是完全违反了CoreData的线程安全约束,所有托管对象上下文(NSManagedObjectContext)和托管对象的操作都必须在对应上下文的调度队列内执行,你的代码多处存在跨队列访问的问题:
- Task的async便利初始化方法里,直接在全局调度队列创建托管对象、修改属性,没有放入私有上下文的执行队列
searchUser返回的User对象是私有上下文的托管对象,你在perform块外部给Task的user属性赋值,属于跨队列访问托管对象,多次迭代后对象被自动释放就会触发EXC_BAD_ACCESS错误- 全局持有
tasks数组存储大量托管对象,托管对象依赖上下文存活,上下文刷新或内存释放后数组内的对象会变为野指针,访问就会崩溃
修复方案
把所有和私有上下文、托管对象相关的操作全部移入私有上下文的perform块内执行,不要跨队列操作任何托管对象,移除无意义的全局托管对象数组。
修复后代码示例
数据导入主逻辑
let pc = PersistenceController.shared func importTasks() async { do { let urlArray = ListURL.urlTest // 所有私有上下文操作全部放入perform块,保证线程安全 try await pc.privateContext.perform { [weak pc] in guard let pc = pc else { return } // 提前做User缓存,避免每条数据都发起查询请求,大幅提升导入效率 var userCache: [String: User] = [:] for url in urlArray { // 同步读取CSV内容,避免跨队列迭代的线程问题 let csvLines = try String(contentsOf: url).components(separatedBy: .newlines) for line in csvLines { guard !line.isEmpty else { continue } let fields = line.components(separatedBy: ",") // 做好边界判断,避免数组越界崩溃 guard fields.count >= 3, !fields[0].isEmpty else { continue } // 直接在当前上下文队列内创建Task对象 let task = Task(context: pc.privateContext) task.taskID = fields[0] task.text = fields[1] let userID = fields[2] // 先查缓存,缓存没有再发起查询 if let cachedUser = userCache[userID] { task.user = cachedUser } else { let request: NSFetchRequest<User> = User.fetchRequest() request.predicate = NSPredicate(format: "id == %@", userID) request.fetchLimit = 1 if let user = try pc.privateContext.fetch(request).first { task.user = user userCache[userID] = user } } } } // 直接在队列内保存私有上下文 try pc.privateContext.save() } // 保存主上下文 try await pc.viewContext.perform { [weak pc] in guard let pc = pc else { return } try pc.viewContext.save() pc.viewContext.refreshAllObjects() } } catch { print("导入失败: \(error.localizedDescription)") } }
额外优化建议
- 如果导入数据量超过1000条,建议每导入500条就执行一次
privateContext.save()+privateContext.reset(),释放内存避免内存占用过高 - 原有的Task异步便利初始化方法可以直接删除,所有托管对象创建逻辑都放到上下文队列内执行
内容的提问来源于stack exchange,提问作者Valerio Pasquini
相关产品推荐
相关产品推荐

