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

CoreData无法加载/创建NSPersistentContainer权限问题求助

CoreData + App Groups 共享数据权限错误排查与解决

问题场景

开发基于CoreData(搭配App Groups)的项目,用于主应用和键盘扩展间共享数据。误删手机应用后重新编译,使用自定义键盘扩展时触发权限错误:

CoreData: error: <NSPersistentStoreCoordinator: 0x303701ff0>: Attempting recovery from error encountered during addPersistentStore: 0x30120e280 Error Domain=NSCocoaErrorDomain Code=513 "The file cannot be saved because you do not have authorisation." UserInfo={reason=No permissions to create file; code = 1}
SmartKeyboard/DataController.swift:37: Fatal error: Unresolved error The file cannot be saved because you do not have authorisation., ["reason": No permissions to create file; code = 1].

现有实现代码

class DataController: ObservableObject {
    enum SetScoreMethod {
        case decrement, increment
    }
    
    static var shared = DataController()
    var moc: NSManagedObjectContext
    @Published var game: Game?
    
    private init() {
        
        let container = NSPersistentContainer(name: "MODEL_NAME")
        if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "my.group.identifier")?.appendingPathComponent("MODEL_NAME.sqlite") {
        
            let description = NSPersistentStoreDescription(url: url)
            description.shouldInferMappingModelAutomatically = true
            description.shouldMigrateStoreAutomatically = true
            description.isReadOnly = false
            
            container.persistentStoreDescriptions = [description]
            container.loadPersistentStores { storeDescription, error in
                if let error = error as NSError? {
                    if FileManager.default.fileExists(atPath: url.path) {
                        try? FileManager.default.removeItem(at: url)
                    }
                    
                    fatalError("Unresolved error \(error.localizedDescription), \(error.userInfo)")
                }
            }
        }

        self.moc = container.viewContext
        fetchOrCreateGame()
    }
    
    func fetchOrCreateGame() -> Void {
        let fetchRequest: NSFetchRequest<Game> = Game.fetchRequest()
        do {
            let results = try self.moc.fetch(fetchRequest)
            if let existingGame = results.first {
                self.game = existingGame
            } else {
                let newGame = Game(context: self.moc)
                newGame.score = 0
                try self.moc.save()
                self.game = newGame
            }
        } catch {
            fatalError("Failed to fetch or create Game entity: \(error)")
        }
    }
    
    public func setScore(_ method: SetScoreMethod,with score: Int) -> Void {
        do {
            if method == .increment {
                self.game!.score += Int16(score)
            }
            
            if method == .decrement {
                if self.game!.score >= score {
                    self.game!.score -= Int16(score)
                }
            }
            
            try self.moc.save()
        } catch {
            fatalError("Failed to save new score: \(error)")
        }
        
    }
}

解决步骤

1. 验证App Groups配置一致性

  • 检查主应用和键盘扩展的Signing & Capabilities面板,确认两者都添加了完全相同的App Groups标识符(my.group.identifier),且状态为启用。
  • 确保两个目标的Team ID一致,标识符拼写无大小写、符号错误。

2. 预创建共享目录

键盘扩展沙箱权限比主应用严格,无法直接创建App Groups目录。在获取存储URL后,先创建父目录:

if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "my.group.identifier")?.appendingPathComponent("MODEL_NAME.sqlite") {
    // 确保父目录存在
    let parentDir = url.deletingLastPathComponent()
    if !FileManager.default.fileExists(atPath: parentDir.path) {
        try? FileManager.default.createDirectory(at: parentDir, withIntermediateDirectories: true)
    }
    
    // 原有PersistentStoreDescription配置逻辑
    let description = NSPersistentStoreDescription(url: url)
    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true
    description.isReadOnly = false
    
    container.persistentStoreDescriptions = [description]
    // ... 后续加载逻辑
}

3. 优化错误重试逻辑

当前错误处理删除文件后直接终止,修改为删除后重试加载:

container.loadPersistentStores { storeDescription, error in
    if let error = error as NSError? {
        if FileManager.default.fileExists(atPath: url.path) {
            try? FileManager.default.removeItem(at: url)
            // 删除后重新尝试加载存储
            container.loadPersistentStores { _, retryError in
                if let retryError = retryError as NSError? {
                    fatalError("Unresolved error after retry \(retryError.localizedDescription), \(retryError.userInfo)")
                }
            }
        } else {
            fatalError("Unresolved error \(error.localizedDescription), \(error.userInfo)")
        }
    }
}

4. 优先启动主应用初始化存储

首次部署后,先打开主应用完成CoreData存储的初始化(主应用沙箱权限更高,能顺利创建目录和文件),再使用键盘扩展。

5. 清理缓存重新部署

  • 删除Xcode的Derived Data:Xcode > 设置 > 位置 > Derived Data > 删除对应项目文件夹。
  • 彻底删除手机上的主应用和键盘扩展,重新编译安装。

核心原因

误删应用后,App Groups共享目录可能残留无权限文件;或键盘扩展尝试创建文件时,父目录未被主应用预先创建,而键盘扩展沙箱限制无法自行创建目录,导致权限不足。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 07:31:35