SwiftData崩溃:在带有 @Observable的 ViewModel的 AsyncStream中的Task隔离闭包?

移动开发 2026-07-09

我快要疯了。我刚刚把项目升级,改用新的 @Observable 宏和 SwiftData,以取代CoreData。一切都运行正常,直到我尝试实现一个使用 AsyncStream 的实时网络同步来流式传输数据更新的功能。

每当流产生一个新项,我尝试在我的 ModelContext 中插入或更新它时,应用就会因为一个离谱的并发错误崩溃,甚至直接 EXC_BAD_ACCESS

下面是我的设置。我有一个 NetworkMonitor,通过一个异步流产生IDs。我的视图模型(用 @Observable 标记)内部,在一个 Task 中监听这个流。

// My Model
@Model
class TodoItem {
    var id: UUID
    var title: String
    var isDone: Bool

    init(id: UUID, title: String, isDone: Bool = false) {
        self.id = id
        self.title = title
        self.isDone = isDone
    }
}

// The ViewModel causing the crash
@Observable
class TodoViewModel {
    var items: [TodoItem] = []
    private var modelContext: ModelContext
    private var networkSync = NetworkSyncManager() // returns AsyncStream<String>

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    func listenToSync() {
        Task {
            // Crash happens somewhere inside here
            for await rawUpdate in networkSync.updateStream {
                let newItem = TodoItem(id: UUID(), title: "Synced: \(rawUpdate)")

                // CRASH HERE: "Mutation of captured var 'self' in concurrently-executing code"
                // Or sometimes: Thread 3: Fatal error: Illegal attempt to map a non-Sendable type
                self.modelContext.insert(newItem) 
                try? self.modelContext.save()
            }
        }
    }
}

如果我移除 self.modelContext.insert(newItem),循环就能正常运行并打印出这些字符串。

我到底做错了什么?@Observable 会不会让视图模型成为MainActor?如果 Task 继承了MainActor,为什么还在抱怨并发执行的代码?我试着在类中添加 @MainActor,但在 for await 行会出现编译错误,提示 MainActor-isolated property cannot be mutated from a non-isolated context

任何帮助都将不胜感激,我现在完全陷入僵局。

解决方案

我认为把 ModelContainer 注入到视图模型中,然后在需要时再创建一个 ModelContext 对象,会更好。

@Observable
class TodoViewModel {
    var items: [TodoItem] = []
    private var modelContainer: ModelContainer
    private var networkSync = NetworkSyncManager() // returns AsyncStream<String>

    init(modelContainer: ModelContainer) {
        self.modelContainer = modelContainer
    }

    func listenToSync() {
        Task {
            let modelContext = ModelContext(modelContainer)

            for await rawUpdate in networkSync.updateStream {
                let newItem = TodoItem(id: UUID(), title: "Synced: \(rawUpdate)")

                self.modelContext.insert(newItem)
                try? self.modelContext.save()
            }
        }
    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章