如何为NSPersistentContainer

时间:2016-10-02 16:36:58

标签: core-data swift3

如何设置自定义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共享目录。

4 个答案:

答案 0 :(得分:17)

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

let description = NSPersistentStoreDescription(url: myURL)

然后,使用NSPersistentContainer的{​​{1}}属性告诉它使用此自定义位置。

persistentStoreDescriptions

注意:container.persistentStoreDescriptions = [description] 必须提供完整的myURL,即使它尚不存在。仅设置父目录不起作用。

答案 1 :(得分:7)

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

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)")
    }
}

答案 2 :(得分:2)

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

enter image description here

以下代码用于创建db:

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" as" persistentStore"

这可以解释为什么db" defaultDatabase"在我的情况下,如果您想要指定自定义的db文件,或由于文件夹已经存在而导致崩溃,则PersistentContainer无法创建。我通过附加文件名" 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:

enter image description here

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

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

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

答案 3 :(得分:0)

这是我用来初始化可以正常工作的预填充sqlite db的代码。假设您将此数据库用作只读数据库,则无需将其复制到设备上的Documents目录中。

substr(x, 3*(y-1)+1, 3*y)

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

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