如何使用 SQLite 文件预加载 Core Data,该文件引用使用“外部存储”保存的图像?

Posted

技术标签:

【中文标题】如何使用 SQLite 文件预加载 Core Data,该文件引用使用“外部存储”保存的图像?【英文标题】:How to pre-load Core Data with a SQLite file that have references to images that were saved using "external storage"? 【发布时间】:2021-10-17 00:18:53 【问题描述】:

我的目标是在首次应用启动时预加载 Core Data。到目前为止,我运行了一个模拟并用数据填充了核心数据。 (我已经检查了“允许外部存储”)。

我进入 application_support 并复制了:MyApp.sqlite-wal、MyApp.sqlite-shm、.MyApp_SUPPORT/_EXTERNAL_DATA/ 和 MyApp.sqlite。

然后我在我的应用程序包中添加了 MyApp.sqlite 文件,并在我的应用程序委托中添加了此代码:

lazy var persistentContainer: NSPersistentContainer = 
        let modelName = "MyApp"

        var container: NSPersistentContainer!

        container = NSPersistentContainer(name: modelName)
        
        
        // Preloading
        let appName: String = "MyApp"
        var persistentStoreDescriptions: NSPersistentStoreDescription

        let storeUrl = self.getDocumentsDirectory().appendingPathComponent("MyApp.sqlite")

        if !FileManager.default.fileExists(atPath: (storeUrl.path)) 
            let seededDataUrl = Bundle.main.url(forResource: appName, withExtension: "sqlite")
            try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
        

        let description = NSPersistentStoreDescription()
        description.shouldInferMappingModelAutomatically = true
        description.shouldMigrateStoreAutomatically = true
        description.url = storeUrl

        container.persistentStoreDescriptions = [description]
        //End Preloading

        container.loadPersistentStores(completionHandler:  (storeDescription, error) in
            if let error = error as NSError? 

                fatalError("Unresolved error \(error), \(error.userInfo)")
            
        )
        return container
    ()

它可以工作,但它似乎找不到保存在外部存储中的图像。它们作为参考存在于 .MyApp_SUPPORT/_EXTERNAL_DATA 中。 我应该在哪里添加参考文献?

加载一切是我的目标。

【问题讨论】:

【参考方案1】:

如果你有一个名为MyApp.sqlite 的文件在某个目录(这里是应用程序包)中有外部二进制存储,Core Data 会将这些文件放在一个名为.MyApp_SUPPORT/_EXTERNAL_DATA/ 的子目录中。您需要递归地复制该目录及其子目录中的所有内容。

不过,使用该路径并不是一个好主意,因为它没有记录在案,并且可能会在没有警告的情况下更改。此外,这将错过MyApp.sqlite-walMyApp.sqlite-shm(如果存在)。

一个更好的主意是将种子存储放在它自己的自定义目录中,然后从 那个 目录中复制所有内容。您将拥有一个名为MyAppSeedData 的目录,而不是仅仅拥有MyApp.sqlite,其中将包含MyApp.sqlite。它还将包含 Core Data 所需的所有其他内容,例如外部二进制文件。使用相同的FileManager函数复制MyAppSeedData目录(因为它会递归复制每个文件),你应该没问题。

复制文件夹的代码是这样的:

if let sourceDirURL = Bundle.main.url(forResource: "Source Folder", withExtension: nil) 
    let destinationDirURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TestFolder")
    if !FileManager.default.fileExists(atPath: destinationDirURL.path) 
        do 
            try FileManager.default.copyItem(at: sourceDirURL, to: destinationDirURL)
         catch 
            print("Error copying directory: \(error)")
        
    

然后您可以将 SQLite 文件名添加到 destinationDirURL 的末尾并将其用于 Core Data。

【讨论】:

感谢您的回答,所以目前我有 MyApp.sqlite-wal、MyApp.sqlite-shm、.MyApp_SUPPORT/_EXTERNAL_DATA/ 和 MyApp.sqlite。我从以前构建的 application_support 中复制了这些文件。我将所有这些粘贴到 MyAppSeedData 文件夹中,并将其拖到 Xcode 中 AppDelegate 下的包中。然后我修改了 .appendingPathComponent("MyApp.sqlite") 并让 seededDataUrl = Bundle.main.url(forResource: "PackTagsSeedData", withExtension: nil) 但我得到了一个致命错误:“MyAppSeedData”无法打开。”。我不知道我做对了没有? 听起来是对的,在这里进行快速测试,它似乎有效。当您将文件夹拖入 Xcode 中时,您是否将其添加到应用程序的目标中?您可以在 Xcode 窗口右侧的“目标成员资格”下的文件检查器中进行检查。当您选择窗口左侧的文件夹时,应在右侧检查应用程序目标。 Yes MyAppSeedData 文件夹已添加到应用目标。 我添加了一些代码以防万一。如果没有,我不知道问题是什么。复制这样的持久存储并使用它绝对应该可以工作。如果没有,我没有足够的关于您的代码的信息来说明原因。 我成功了,Core Data 通常将 .sqlite 和外部引用存储在库目录中的“应用程序支持”目录中,所以我做了一个文件夹“MyAppSeedData”,粘贴了.sqlite、_SUPPORT、-smh、-wal 里面的文件,把它带到了包中。在 Appdelegate 中,我添加了创建“应用程序支持”目录的代码,因为它在首次启动时还不存在,并且代码将所有内容从捆绑包中的目录复制到刚刚创建的目录。通过这种方式,应用程序可以找到一个预先填充的数据库以及它们应该存在的所有文件。我发布了我使用的代码。【参考方案2】:

第 1 步:创建“MyAppSeedData”目录并粘贴 MyApp.sqlite、MyApp_SUPPORT、MyApp.sqilte-smh、MyApp.sqilte-wal 文件。

第 2 步:将 MyAppSeedData 拖到 AppDelegate 下的包中并勾选添加目标框。

第 3 步:这些函数必须在 AppDelegate 文件中:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

    //If first launch condition == true 
    seedData()
    //
    return true


    
func seedData() 
    let fm = FileManager.default
    
    //Destination URL: Application Folder
    let libURL = fm.urls(for: .libraryDirectory, in: .userDomainMask).first!
    let destFolder = libURL.appendingPathComponent("Application Support").path
    //Or
    //let l1 = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last!
    //
    
    //Starting URL: MyAppSeedData dir
    let folderPath = Bundle.main.resourceURL!.appendingPathComponent("MyAppSeedData").path
    
    let fileManager = FileManager.default
        let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
        if let applicationSupportURL = urls.last 
            do
                try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
            
            catch
                print(error)
            
        
    copyFiles(pathFromBundle: folderPath, pathDestDocs: destFolder)



func copyFiles(pathFromBundle : String, pathDestDocs: String) 
    let fm = FileManager.default
    do 
        let filelist = try fm.contentsOfDirectory(atPath: pathFromBundle)
        let fileDestList = try fm.contentsOfDirectory(atPath: pathDestDocs)

        for filename in fileDestList 
            try FileManager.default.removeItem(atPath: "\(pathDestDocs)/\(filename)")
        
        
        for filename in filelist 
            try? fm.copyItem(atPath: "\(pathFromBundle)/\(filename)", toPath: "\(pathDestDocs)/\(filename)")
        
     catch 
        print("Error info: \(error)")
    





// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = 
    let modelName = "MyApp"

    var container: NSPersistentContainer!

    container = NSPersistentContainer(name: modelName)
            
    container.loadPersistentStores(completionHandler:  (storeDescription, error) in
        if let error = error as NSError? 
            fatalError("Unresolved error \(error), \(error.userInfo)")
        
    )
    return container
()

【讨论】:

以上是关于如何使用 SQLite 文件预加载 Core Data,该文件引用使用“外部存储”保存的图像?的主要内容,如果未能解决你的问题,请参考以下文章

Core Data 预填充的 SQLite 问题,z-metadata

可以将 Core Data 与不同的预填充 sqlite db 一起使用吗?

在 Swift 中解析 CSV 文件并将其加载到 Core Data 中

如何使用 swift 3 xcode 8 在核心数据中预加载数据库

如何使用 Restkit 和 Core Data 存储对象和种子预加载数据

带有预加载 sqlite3 数据库的 iOS 单元测试