Swift 4 CoreData预加载SQLite数据库失败求助
我之前也踩过一模一样的坑!看起来你的文件复制逻辑虽然输出了成功提示,但Core Data实际上没连上你复制过去的数据库,或者复制过程有隐藏问题没覆盖掉默认的空库。咱们一步步排查解决:
1. 先确认数据库文件的复制路径和Core Data实际路径完全一致
Core Data的持久化存储默认存在Application Support目录下,很多人会在这里写错路径(比如用了Documents目录,或者容器名拼写错误)。你可以先打印出Core Data实际使用的存储URL,对比你的复制目标路径:
let persistentContainer = NSPersistentContainer(name: "leksikon") print("Core Data 实际存储URL: \(persistentContainer.persistentStoreDescriptions.first?.url ?? URL(fileURLWithPath: "Unknown"))")
注意:Core Data使用的容器名是你的模型名leksikon,不是预加载文件名leksikonPreload,复制时要把预加载的三个文件都重命名成对应leksikon前缀的文件名。
2. 必须在Core Data加载持久化存储之前完成复制
如果preloadDBData()的调用时机晚于persistentContainer.loadPersistentStores(),Core Data会先创建空数据库,之后你再复制预加载文件会因为文件被占用而失败(哪怕你的函数输出了"FILES EXIST",实际是空文件)。调整代码顺序:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 第一步:执行预加载 preloadDBData() // 第二步:加载Core Data存储 let persistentContainer = NSPersistentContainer(name: "leksikon") persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Core Data加载失败: \(error), \(error.userInfo)") } // 在这里查询数据才准确 let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "你的实体名") do { let dataCount = try persistentContainer.viewContext.count(for: fetchRequest) print("当前数据条数: \(dataCount)") } catch { print("查询失败: \(error)") } }) return true }
3. 确保三个文件都完整复制(不能只复制.sqlite)
sqlite-wal和sqlite-shm是Write-Ahead Log相关的必要文件,缺了它们Core Data会判定数据库损坏,直接创建新空库。你的复制函数要同时处理三个文件:
func preloadDBData() { let fileManager = FileManager.default let persistentContainer = NSPersistentContainer(name: "leksikon") // 获取Core Data存储的三个目标文件路径 guard let targetSQLiteURL = persistentContainer.persistentStoreDescriptions.first?.url else { return } let targetWALURL = targetSQLiteURL.deletingPathExtension().appendingPathExtension("sqlite-wal") let targetSHMURL = targetSQLiteURL.deletingPathExtension().appendingPathExtension("sqlite-shm") // 获取Bundle里的预加载文件路径 guard let sourceSQLite = Bundle.main.url(forResource: "leksikonPreload", withExtension: "sqlite"), let sourceWAL = Bundle.main.url(forResource: "leksikonPreload", withExtension: "sqlite-wal"), let sourceSHM = Bundle.main.url(forResource: "leksikonPreload", withExtension: "sqlite-shm") else { print("预加载文件不在Bundle中") return } // 检查目标文件是否存在,不存在则复制 if !fileManager.fileExists(atPath: targetSQLiteURL.path) { do { // 创建目标目录(如果不存在) try fileManager.createDirectory(at: targetSQLiteURL.deletingLastPathComponent(), withIntermediateDirectories: true) // 复制三个文件 try fileManager.copyItem(at: sourceSQLite, to: targetSQLiteURL) try fileManager.copyItem(at: sourceWAL, to: targetWALURL) try fileManager.copyItem(at: sourceSHM, to: targetSHMURL) print("FILES COPIED") } catch { print("复制失败: \(error)") } } else { print("FILES EXIST") } }
4. 验证预加载数据库和Core Data模型完全匹配
用SQLite Browser打开leksikonPreload.sqlite,对比项目里的.xcdatamodeld模型:实体名、属性名、属性类型必须完全一致。如果有差异,Core Data会尝试自动迁移,迁移失败就会生成空库。
5. 清理应用数据后重新测试
之前运行时Core Data已经生成了空数据库,即使修复了复制逻辑,旧文件依然存在。先删除模拟器/真机上的应用,重新安装再测试。
内容的提问来源于stack exchange,提问作者Владислав Форафонтов

