CoreData无法加载/创建NSPersistentContainer权限问题求助
问题场景
开发基于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

