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

SwiftData共享扩展致App无法打开SQLite文件,求解决及示例代码

SwiftData 共享扩展正确实现方案及示例代码

问题根源分析

你的核心问题在于App和共享扩展的ModelContainer配置不一致:

  1. 扩展初始化时仅传入Item(代码中SavedItem应为笔误),但App包含ClassA/ClassB/ClassC和Item多组模型,导致扩展创建的数据库缺少Class系列表,App访问时触发「找不到表」错误。
  2. 未显式指定App Group共享存储路径,默认的default.store可能不在共享容器内,引发权限或文件不一致问题。
  3. 扩展与App的数据库文件未统一,导致App读取到扩展创建的不完整数据库。

前置准备

在Xcode中为App和共享扩展开启相同的App Group:

  • 打开项目设置 → 选中主Target → Signing & Capabilities → 添加App Groups → 启用并填写统一的group ID(例如group.com.yourapp.shared)。
  • 对共享扩展Target执行相同操作,确保使用同一个group ID。

统一模型定义

所有需要共享的模型需放在App和扩展都能访问的位置(如共享源文件或独立Swift Package):

import SwiftData

@Model final class ClassA {
    @Attribute(.unique) 
    var id: String
    
    @Relationship(deleteRule: .cascade, inverse: \ClassB.parent)
    var things: [ClassB] = [ClassB]()
    
    init(id: String) {
        self.id = id
    }
}

@Model final class ClassB {
    @Attribute(.unique)
    var id: String
   
    var parent: ClassA?

    @Relationship(deleteRule: .cascade, inverse: \ClassC.parent)
    var otherThings: [ClassC] = [ClassC]()
    
    init(id: String) {
        self.id = id
    }
}

@Model final class ClassC {
    @Attribute(.unique)
    var id: String

    var parent: ClassB?
    
    init(id: String) {
        self.id = id
    }
}

@Model final class Item {
    @Attribute(.unique)
    var id: UUID
    var itemUrl: String
    
    init(id: UUID, itemUrl: String) {
        self.id = id
        self.itemUrl = itemUrl
    }
}

App端ModelContainer配置

在App入口(如App.swift)初始化全局ModelContainer,指定共享路径:

import SwiftUI
import SwiftData

@main
struct YourApp: App {
    let modelContainer: ModelContainer
    
    init() {
        // 获取App Group共享容器路径
        guard let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourapp.shared") else {
            fatalError("Failed to get shared container URL")
        }
        let storeURL = sharedContainerURL.appending(path: "shared.store")
        
        // 配置ModelConfiguration,包含所有共享模型
        let config = ModelConfiguration(url: storeURL)
        do {
            modelContainer = try ModelContainer(for: ClassA.self, ClassB.self, ClassC.self, Item.self, configurations: config)
        } catch {
            fatalError("Failed to create model container: \(error)")
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(modelContainer)
    }
}

共享扩展端配置

在ShareViewController中使用与App完全一致的共享路径和模型集合初始化ModelContainer:

import UIKit
import Social
import SwiftData

class ShareViewController: SLComposeServiceViewController {
    private let modelContainer: ModelContainer
    private let modelContext: ModelContext
    
    required init?(coder: NSCoder) {
        // 获取共享容器路径,与App端完全一致
        guard let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourapp.shared") else {
            fatalError("Failed to get shared container URL")
        }
        let storeURL = sharedContainerURL.appending(path: "shared.store")
        
        let config = ModelConfiguration(url: storeURL)
        do {
            // 必须包含所有共享模型,与App端一致
            modelContainer = try ModelContainer(for: ClassA.self, ClassB.self, ClassC.self, Item.self, configurations: config)
        } catch {
            fatalError("Failed to create model container: \(error)")
        }
        modelContext = modelContainer.mainContext
        
        super.init(coder: coder)
    }
    
    override func isContentValid() -> Bool {
        return true
    }
    
    override func didSelectPost() {
        // 获取分享的URL
        guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
              let provider = item.attachments?.first,
              provider.hasItemConformingToTypeIdentifier("public.url") else {
            extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
            return
        }
        
        provider.loadItem(forTypeIdentifier: "public.url") { [weak self] (url, error) in
            guard let self = self,
                  let url = url as? URL,
                  let itemUrl = url.absoluteString.removingPercentEncoding else {
                self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
                return
            }
            
            self.saveItem(itemUrl: itemUrl)
            self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
        }
    }
    
    private func saveItem(itemUrl: String) {
        let fetch = FetchDescriptor<Item>(
            predicate: #Predicate { $0.itemUrl == itemUrl },
            includePendingChanges: true
        )
        
        do {
            let dupes = try modelContext.fetch(fetch)
            guard dupes.isEmpty else { return }
            
            let newItem = Item(id: UUID(), itemUrl: itemUrl)
            modelContext.insert(newItem)
            try modelContext.save()
        } catch {
            print("Save item error: \(error)")
        }
    }
}

关键注意事项

  • 统一模型集合:App和扩展的ModelContainer必须初始化所有共享模型,否则会导致数据库表结构不一致。
  • 共享路径一致性:必须显式指定App Group的存储路径,不能依赖默认路径,确保两者访问同一个数据库文件。
  • 线程安全:ModelContext不能跨线程访问,若需在扩展后台线程操作,建议创建独立上下文:
    let backgroundContext = ModelContext(modelContainer)
    backgroundContext.autosaveEnabled = false
    DispatchQueue.global().async {
        // 使用backgroundContext执行操作
        do {
            try backgroundContext.save()
        } catch {
            print("Background save error: \(error)")
        }
    }
    
  • 避免强制try:生产环境中不要使用try!,需用do-catch处理错误,防止崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 10:34:52