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

异步存储Core Data时实体关联关系失效问题求助

Core Data异步处理大文件时关联关系失效问题修复

问题描述

我正在开发一款iOS Swift应用,数据模型包含GameModule和GameModuleFile两个实体,二者为一对多关联。从磁盘加载ZIP文件并解压后,将文件数据存入GameModuleFile实体并关联至最新的GameModule。同步执行时一切正常,但异步执行时,关联关系未被存入Core Data且无报错;有趣的是,小ZIP文件(数百个文件)关联正常,大文件(数千个文件)则失效。

问题代码

import SwiftUI
import UniformTypeIdentifiers

struct GameModuleListView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: [SortDescriptor(\.order)]) var gameModules: FetchedResults<GameModule>
    @State private var loadModuleTrigger = false

    var body: some View {
        NavigationView {
            List {
                ForEach(gameModules, content: GameModuleRow.init)
            }
        }
            .navigationViewStyle(StackNavigationViewStyle())
            .fileImporter(isPresented: $loadModuleTrigger, allowedContentTypes: [.zip], allowsMultipleSelection: false, onCompletion: loadModule)
    }
    
    func loadModule(res: Result<[URL], Error>) {
        let url = try! res.get()[0]
        DispatchQueue.global(qos: .userInitiated).async {
            try? gameModules[gameModules.count - 1].loadFiles(context: moc, url: url)
        }
    }
}

import ZIPFoundation

extension GameModule {
    func loadFiles(context moc: NSManagedObjectContext, url: URL) throws {
        let archive = try GameModule.getArchiveFromUrl(url: url)
        for entry in archive {
            if entry.path.last! != "/" {
                _ = try? archive.extract(entry) { data in
                    let file = GameModuleFile(context: moc)
                    file.filePath = entry.path
                    file.blob = data
                    file.ownerModule = self
                }
            }
        }
        try? moc.save()
    }
}

修复后代码

import SwiftUI
import UniformTypeIdentifiers

struct GameModuleListView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(sortDescriptors: [SortDescriptor(\.order)]) var gameModules: FetchedResults<GameModule>
    @State private var loadModuleTrigger = false

    var body: some View {
        NavigationView {
            List {
                ForEach(gameModules, content: GameModuleRow.init)
            }
        }
            .navigationViewStyle(StackNavigationViewStyle())
            .fileImporter(isPresented: $loadModuleTrigger, allowedContentTypes: [.zip], allowsMultipleSelection: false, onCompletion: loadModule)
    }
    
    func loadModule(res: Result<[URL], Error>) {
        moc.perform {
            let newModule = try! GameModule(moc)
            let url = try! res.get()[0]
            try? newModule.loadFiles(context: moc, url: url)
            try? moc.save()
        }
    }
}

import ZIPFoundation

extension GameModule {
    func loadFiles(context moc: NSManagedObjectContext, url: URL) throws {
        let archive = try GameModule.getArchiveFromUrl(url: url)
        for entry in archive {
            if entry.path.last! != "/" {
                _ = try? archive.extract(entry) { data in
                    let file = GameModuleFile(context: moc)
                    file.filePath = entry.path
                    file.blob = data
                    file.ownerModule = self
                }
            }
        }
    }
}

关键修复点说明

  • 线程安全问题解决:Core Data上下文并非线程安全,原代码直接在全局队列操作UI线程的上下文,导致数据竞争。修复后使用moc.perform确保所有Core Data操作都在上下文所属线程执行。
  • 保存时机调整:将moc.save()移至perform闭包内,保证所有文件实体创建和关联操作完成后再统一保存上下文,避免部分数据未持久化。
  • 托管对象访问规范:原代码跨线程访问gameModules中的托管对象存在风险,修复后在perform闭包内创建新的GameModule实例,符合Core Data线程访问规则。

内容的提问来源于stack exchange,提问作者Honza Skýpala

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 16:25:55