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

删除带关联关系的List项致SwiftData+CloudKit应用崩溃求助

SwiftData + NavigationSplitView 删除崩溃问题排查与修复

问题核心分析

你遇到的偶发崩溃本质是数据更新时序不匹配+模型操作副作用违规导致的:

  • 删除操作触发多次视图更新,导致已删除的SheetItem仍被视图引用
  • 计算属性中直接修改模型状态、创建关联对象,且未处理对象已被删除的无效状态
  • CloudKit异步同步可能加剧本地数据与视图的不一致性

代码问题逐一修复

1. 修正删除逻辑的时序问题

原代码中循环内每次删除都调用save(),会多次触发@Query更新,导致视图和数据不同步。改为批量删除后统一保存:

private func deleteItems(offsets: IndexSet) {
    let itemsToDelete = offsets.map { items[$0] }
    
    // 先清空选中状态,避免视图引用已删除对象
    for item in itemsToDelete {
        if item.sheetNumber == displayedSheetNumber {
            sheetsNavigationViewModel.selection = nil
        }
        modelContext.delete(item)
    }
    
    // 统一保存,减少视图更新次数
    do {
        try modelContext.save()
    } catch {
        print("删除保存失败:\(error.localizedDescription)")
    }
}

2. 移除计算属性的副作用

currentContent计算属性中直接创建SheetContent、修改currentContentId属于违规操作——计算属性不应产生状态变更,且未处理SheetItem已被删除的无效场景。改为方法调用,明确依赖ModelContext:

@Model
final class SheetItem: Identifiable {
    var order: Int?
    var sheetNumber: Int?
    var sheetName: String?
    
    @Relationship(deleteRule: .cascade, inverse: SheetContent.respectiveSheetItem)
    var content: [SheetContent] = [] // 初始化空数组,避免可选值空指针
    var currentContentId: String?
    
    func getCurrentContent(context: ModelContext) -> SheetContent? {
        // 先检查当前对象是否仍在上下文有效(未被删除)
        guard context.registeredObjects.contains(self) else {
            return nil
        }
        
        if let currentContentId,
           let foundContent = content.first(where: { $0.contentId?.uuidString == currentContentId }) {
            return foundContent
        } else {
            let newContent = SheetContent(respectiveSheetItem: self)
            context.insert(newContent) // 必须将新对象加入上下文
            content.append(newContent)
            currentContentId = newContent.contentId?.uuidString
            return newContent
        }
    }
}

视图中调用时需验证返回值:

// 在sheetView中使用
if let content = item.getCurrentContent(context: modelContext) {
    // 展示可编辑内容
} else {
    // 对象已删除,显示占位视图
    Text("Sheet has been deleted")
}

3. 优化视图数据源与选中状态

  • 用id: \.id替代id: \.self,避免SwiftData模型删除后self状态异常:
ForEach(items, id: \.id) { item in
    NavigationLink {
        sheetView(for: item)
    } label: {
        Text("Sheet \(item.sheetName ?? "Unnamed")")
    }
}
  • 验证detail视图的选中对象有效性,避免访问已删除数据:
private func navigationDestinationView() -> some View {
    guard let selectedId = sheetsNavigationViewModel.selection?.id,
          let item = items.first(where: { $0.id == selectedId }) else {
        return Text("Select a sheet or create a new one")
    }
    return sheetView(for: item)
}

进一步排查思路

  1. 开启Zombie Objects检测:在Xcode Scheme的Diagnostics中勾选Enable Zombie Objects,定位访问已释放对象的具体位置
  2. CloudKit同步调试:在Scheme Arguments中添加-com.apple.CoreData.CloudKitDebug 1,查看同步过程中的错误日志,确认云端同步是否干扰本地数据状态
  3. 记录数据状态:在删除前后打印items.count、modelContext.registeredObjects,对比视图更新与数据变更的时序差异

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:00:15