如何使用引用使用“外部存储”保存的图像的SQLite文件预加载核心数据?

How to pre-load Core Data with a SQLite file that have references to images that were saved using "external storage"?

提问人:Abv 提问时间:8/14/2021 最后编辑:Abv 更新时间:8/15/2021 访问量:511

问:

我的目标是在首次启动应用程序时预加载 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作为参考。 我应该在哪里添加引用?

加载一切是我的目标。

Swift iPhone SQLite 核心数据 持久化

评论


答:

0赞 Tom Harrington 8/14/2021 #1

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

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

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

复制文件夹的代码如下所示:

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 文件名添加到末尾,并将其用于 Core Data。destinationDirURL

评论

0赞 Abv 8/14/2021
谢谢你的回答,所以目前我有MyApp.sqlite-wal,MyApp.sqlite-shm,.MyApp_SUPPORT/_EXTERNAL_DATA/ 和 MyApp.sqlite。我从以前的版本application_support复制了这些文件。我将所有这些粘贴到 MyAppSeedData 文件夹中,并将其拖到 AppDelegate 下的 Xcode 包中。然后我修改了 .appendingPathComponent(“MyApp.sqlite”) 并让 seededDataUrl = Bundle.main.url(forResource: “PackTagsSeedData”, withExtension: nil) 但我得到了一个致命错误:“MyAppSeedData”无法打开。我不知道我是否做了正确的事情?
0赞 Tom Harrington 8/14/2021
这听起来是正确的,在这里的快速测试中,它似乎有效。当您将文件夹拖到 Xcode 中时,您是否将其添加到应用程序的目标中?您可以在 Xcode 窗口右侧的文件检查器中的“目标成员资格”下进行检查。选择窗口左侧的文件夹时,应检查右侧的应用目标。
0赞 Abv 8/14/2021
是的,MyAppSeedData 文件夹已添加到应用目标。
1赞 Tom Harrington 8/15/2021
我添加了一些代码,以防万一。如果没有,我不知道问题出在哪里。像这样复制一个持久性存储并使用它绝对应该有效。如果没有,我没有足够的关于你的代码的信息来说明原因。
0赞 Abv 8/15/2021
我让它工作,Core Data 通常将 .sqlite 和外部引用存储在位于 Library 目录中的“Application Support”目录中,所以我所做的是我制作了一个文件夹“MyAppSeedData”,粘贴了 .sqlite、_SUPPORT、-smh、-wal 文件,将其带到捆绑包中。在 Appdelegate 中,我添加了代码来创建一个“应用程序支持”目录,因为它在首次启动时尚不存在,并且添加了将捆绑包中的所有内容复制到刚刚创建的目录的代码。这样一来,应用就会找到一个预先填充的数据库,以及它们应该在的位置的所有文件。我发布了我使用的代码。
0赞 Abv 8/15/2021 #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
}()