【问题标题】:How to set a custom store URL for NSPersistentContainer如何为 NSPersistentContainer 设置自定义商店 URL
【发布时间】:2017-02-10 15:58:58
【问题描述】:

如何将自定义 store.sqlite URL 设置为 NSPersistentContainer?

我发现了一个丑陋的方法,继承 NSPersistentContainer:

final public class PersistentContainer: NSPersistentContainer {
private static var customUrl: URL?

public init(name: String, managedObjectModel model: NSManagedObjectModel, customStoreDirectory baseUrl:URL?) {
    super.init(name: name, managedObjectModel: model)
    PersistentContainer.customUrl = baseUrl
}

override public class func defaultDirectoryURL() -> URL {
    return (customUrl != nil) ? customUrl! : super.defaultDirectoryURL()
}

}

有什么好的方法吗?

背景:我需要保存到 App Groups 共享目录。

【问题讨论】:

    标签: core-data swift3


    【解决方案1】:

    您可以使用 NSPersistentStoreDescription 类来执行此操作。它有一个初始化程序,您可以使用它来提供持久存储文件应该去的文件 URL。

    let description = NSPersistentStoreDescription(url: myURL)
    

    然后,使用NSPersistentContainerpersistentStoreDescriptions 属性告诉它使用这个自定义位置。

    container.persistentStoreDescriptions = [description]
    

    注意:myURL 必须提供完整的/path/to/model.sqlite,即使它还不存在。只设置父目录是不行的。

    【讨论】:

    • 这似乎只在 store.sqlite 已经存在的情况下才有效,而不是在初始化 CoreData 堆栈时。
    • 不,如果文件不存在,它可以正常工作。但是你希望文件所在的目录必须存在,并且你的应用组需要正确设置。不过,商店文件不需要已经存在。
    • 奇怪。它在这里崩溃并出现“找不到文件”错误。如果文件已经存在,则工作正常。
    • 这就是它应该如何工作的。如果它对您不起作用,那么您还有另一个问题。可能目录不存在。
    【解决方案2】:

    扩展 Tom 的答案,当您出于任何目的使用 NSPersistentStoreDescription 时,请务必使用 NSPersistentStoreDescription(url:) 进行初始化,因为根据我的经验,如果您根据该描述使用基本初始化程序 NSPersistentStoreDescription()loadPersistentStores(),它将下次构建和运行时覆盖现有的持久性存储及其所有数据。这是我用于设置 URL 和描述的代码:

    let container = NSPersistentContainer(name: "MyApp")
    
    let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
    let url = storeDirectory.appendingPathComponent("MyApp.sqlite")
    let description = NSPersistentStoreDescription(url: url)
    description.shouldInferMappingModelAutomatically = true
    description.shouldMigrateStoreAutomatically = true
    container.persistentStoreDescriptions = [description]
    
    container.loadPersistentStores { (storeDescription, error) in
        if let error = error as? NSError {
            print("Unresolved error: \(error), \(error.userInfo)")
        }
    }
    

    【讨论】:

      【解决方案3】:

      我刚刚发现PersistentContainer创建的db的位置与UIManagedDocument创建的db不同。这是UIManagedDocument的数据库位置快照:

      以下代码用于创建数据库:

      let fileURL = db.fileURL // url to ".../Documents/defaultDatabase"
      let fileExist = FileManager.default.fileExists(atPath: fileURL.path)
      if fileExist {
          let state = db.documentState
          if state.contains(UIDocumentState.closed) {
              db.open()
          }
      } else {
          // Create database
          db.save(to: fileURL, for:.forCreating)
      }
      

      看起来PersistentContainer 引用的数据库实际上是文件夹“StoreContent”下的文件“persistentStore”

      这可以解释为什么在我的情况下,如果您想指定自定义数据库文件,PersistentContainer 无法创建数据库“defaultDatabase”,或者由于文件夹已经存在而导致崩溃。我通过像这样附加一个文件名“MyDb.sqlite”进一步验证了这一点:

      let url = db.fileURL.appendingPathComponent("MyDb.sqlite")
      let storeDesription = NSPersistentStoreDescription(url: url)
      container.persistentStoreDescriptions = [storeDesription]
      print("store description \(container.persistentStoreDescriptions)"
      // store description [<NSPersistentStoreDescription: 0x60000005cc50> (type: SQLite, url: file:///Users/.../Documents/defaultDatabase/MyDb.sqlite)
      container.loadPersistentStores() { ... }
      

      这是新的 MyDb.sqlite:

      根据上面的分析,如果你有这样的代码:

      if #available(iOS 10.0, *) {
          // load db by using PersistentContainer
          ...
       } else {
          // Fallback on UIManagedDocument method to load db
          ...
       }
      

      用户的设备可能在 iOS 10.0 之前,之后更新到 10+。对于此更改,我认为必须调整 url 以避免崩溃或创建新的(空)数据库(丢失数据)。

      【讨论】:

        【解决方案4】:

        这是我用来初始化一个预填充的 sqlite 数据库的代码,该数据库始终如一地工作。假设您将此数据库用作只读数据库,则无需将其复制到设备上的 Documents 目录中。

        let repoName = "MyPrepopulatedDB"
        let container = NSPersistentContainer(name: repoName)
        let urlStr = Bundle.main.path(forResource: "MyPrepopulatedDB", ofType: "sqlite")
        let url = URL(fileURLWithPath: urlStr!)
        let persistentStoreDescription = NSPersistentStoreDescription(url: url)
        persistentStoreDescription.setOption(NSString("true"), forKey: NSReadOnlyPersistentStoreOption)
        container.persistentStoreDescriptions = [persistentStoreDescription]
        
        container.loadPersistentStores(completionHandler: { description, error in
          if let error = error {
            os_log("ERROR: Failed to initialize persistent store, error is \(error.localizedDescription)")
          } else {
            os_log("Successfully loaded persistent store, \(description)")
          }
        })
        

        需要牢记的一些非常重要的步骤/项目:

        • 在构造 sqlite 文件的 URL 时,使用初始化程序的 URL(fileURLWithPath:) 形式。核心数据似乎需要基于文件的 URL,否则会报错。
        • 我使用单元测试来运行一些代码,以便在模拟器中创建/预填充数据库。
        • 我通过在 loadPersistentStores() 的完成块中添加打印语句找到了 sqlite 文件的完整路径。此块的描述参数包含 sqlite 文件的完整路径。
        • 然后,您可以使用 Finder 将该文件复制/粘贴到应用项目中。
        • 在与 sqlite 文件相同的位置还有另外两个文件(.sqlite-shm 和 .sqlite-wal)。将这两个也添加到项目中(与 sqlite 文件在同一目录中)。没有它们,核心数据会引发错误。
        • 在persistentStoreDescription中设置NSReadOnlyPersistentStoreOption(如上图)。如果没有这个,您会收到警告(未来可能出现致命错误)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-12-12
          • 1970-01-01
          • 2019-09-03
          • 1970-01-01
          • 2020-09-11
          • 2011-11-13
          • 2018-07-24
          相关资源
          最近更新 更多