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

SwiftData带唯一属性Model二次创建时崩溃原因排查

SwiftData 中带唯一属性的Model实例返回导致二次运行崩溃的原因分析

问题背景

在SwiftData中创建带有@Attribute(.unique)标记的uuid属性的MyModel实例并返回,首次运行应用正常,第二次运行时触发EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP)崩溃。

可复现代码

import SwiftUI
import SwiftData

@available(iOS 17, *)
@available(macOS 11, *)
@Model
public final class MyModel : CustomStringConvertible {
    @Attribute(.unique) var uuid: UUID

    init(uuid: UUID) throws {
        self.uuid = uuid
    }

    public var description: String {
        return self.uuid.uuidString
    }
}

@available(iOS 17, *)
@available(macOS 11, *)
@ModelActor
public actor LocalDatabaseService {
    public static let shared = LocalDatabaseService()

    let schema = Schema([MyModel.self])

    public init() {
        self.modelContainer = try! ModelContainer(for: self.schema)
        let context = ModelContext(modelContainer)
        self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
    }


    public func createMyModel(uuid: UUID) throws -> MyModel {
        let myModel = try MyModel(uuid: uuid)

        let modelContext = self.modelContext
        modelContext.insert(myModel)
        try modelContext.save()

        return myModel
    }
}


struct ContentView: View {
    var body: some View {
        Task {
            let id = UUID(uuidString: "9C66CA5B-D91C-480F-B02C-2D14EEB49902")!
            let myModel = try await LocalDatabaseService.shared.createMyModel(uuid: id)
            print("myModel:\(myModel)")
            print("DONE")
        }
        return VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

#Preview {
    return ContentView()
}

崩溃根本原因

核心问题出在直接返回刚插入并保存的原始Model实例的操作上:

  1. 首次运行时,实例被插入上下文并成功写入持久化存储,此时返回的实例是上下文管理的活跃对象,状态正常。
  2. 第二次运行时,当你用同一个uuid创建新实例并插入上下文,SwiftData会触发唯一约束检查,发现存储中已有对应记录。此时SwiftData内部会尝试将新创建的实例与现有持久化记录进行状态合并,但直接返回原始创建的实例会导致:
    • 新实例还未完成与现有持久化记录的状态同步
    • 上下文内部的对象身份映射表出现冲突,触发SwiftData内部的断言失败,最终表现为EXC_BAD_INSTRUCTION崩溃。

临时解决方案的原理

你采用查询返回实例的方式有效,是因为查询得到的是上下文已经完成状态同步、与持久化记录完全绑定的实例,避免了原始创建实例的状态冲突问题。对应的查询代码:

func getMyModel(uuid: UUID) throws -> MyModel {
    let fetchDescriptor = FetchDescriptor<MyModel>(predicate: #Predicate { $0.uuid == uuid })

    let serverList = try modelContext.fetch(fetchDescriptor)
    if !serverList.isEmpty {
        if let first = serverList.first {
            return first
        }
    }
    fatalError("Could not find MyModel with uuid \(uuid)")
}

更合理的处理方式

在创建带唯一属性的实例时,建议先查询是否存在,存在则直接返回,不存在再创建插入,从根源避免冲突:

public func createOrGetMyModel(uuid: UUID) throws -> MyModel {
    let fetchDescriptor = FetchDescriptor<MyModel>(predicate: #Predicate { $0.uuid == uuid })
    if let existing = try modelContext.fetch(fetchDescriptor).first {
        return existing
    }
    let myModel = try MyModel(uuid: uuid)
    modelContext.insert(myModel)
    try modelContext.save()
    return myModel
}

内容的提问来源于stack exchange,提问作者OlivierM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:45:56