Как задать пользовательский URL-адрес хранилища для NSPersistentContainer

#core-data #swift3

#основные данные #swift3

Вопрос:

Как я могу установить пользовательский URL-адрес store.sqlite для 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()
}
  

}

Есть ли хороший способ?

Справочная информация: мне нужно сохранить в общий каталог групп приложений.

Ответ №1:

Вы делаете это с NSPersistentStoreDescription помощью класса. У него есть инициализатор, который вы можете использовать для предоставления URL-адреса файла, куда должен идти файл постоянного хранилища.

 let description = NSPersistentStoreDescription(url: myURL)
  

Затем используйте NSPersistentContainer persistentStoreDescriptions атрибут ‘s, чтобы указать ему использовать это пользовательское местоположение.

 container.persistentStoreDescriptions = [description]
  

Примечание: myURL необходимо предоставить полный /path/to/model.sqlite , даже если он еще не существует. Установить только родительский каталог не получится.

Комментарии:

1. Похоже, это работает только в том случае, если store.sqlite уже существует, а не при инициализации стека CoreData.

2. Нет, он отлично работает, если файл не существует. Но каталог, в который вы хотите поместить файл, должен существовать, и ваша группа приложений должна быть настроена правильно. Однако нет необходимости, чтобы файл хранилища уже существовал.

3. Странно. Здесь происходит сбой с ошибкой «файл не найден». Работает нормально, если файл уже существует.

4. Вот как это должно работать. Если это не работает для вас, тогда у вас другая проблема. Например, возможно, каталог не существует.

Ответ №2:

Расширяя ответ Тома, при использовании NSPersistentStoreDescription для любых целей обязательно инициализируйте с NSPersistentStoreDescription(url:) помощью, потому что, по моему опыту, если вы используете базовый инициализатор NSPersistentStoreDescription() и loadPersistentStores() на основе этого описания, он перезапишет существующее постоянное хранилище и все его данные при следующем сборке и запуске. Вот код, который я использую для настройки URL и описания:

 let container = NSPersistentContainer(name: "MyApp")
            
let storeDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
// or
let storeDirectory = NSPersistentContainer.defaultDirectoryURL()

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 отличается от базы данных, созданной UIManagedDocument . Вот снимок местоположения БД по 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»

Это может объяснить, почему db «defaultDatabase» в моем случае не может быть создана, PersistentContainer если вы хотите указать свой настроенный файл db или вызвать сбой, поскольку папка уже существует. Я дополнительно проверил это, добавив имя файла «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)")
  }
})
  

Некоторые очень важные шаги / элементы, которые следует иметь в виду:

  • при создании URL-адреса файла sqlite используйте форму URL(fileURLWithPath:) инициализатора. Похоже, что для core data требуются URL-адреса на основе файлов, иначе вы получите сообщение об ошибке.
  • Я использовал модульный тест для запуска некоторого кода для создания / предварительного заполнения базы данных в симуляторе.
  • Я нашел полный путь к файлу sqlite, добавив инструкцию print внутри блока завершения loadPersistentStores() . Параметр описания этого блока содержит полный путь к файлу sqlite.
  • Затем с помощью Finder вы можете скопировать / вставить этот файл в проект приложения.
  • В том же месте, что и файл sqlite, находятся два других файла (.sqlite-shm и .sqlite-wal). Добавьте также эти два в проект (в тот же каталог, что и файл sqlite). Без них core data выдает ошибку.
  • Установите параметр NSReadOnlyPersistentStoreOption в persistentStoreDescription (как показано выше). Без этого вы получите предупреждение (возможная фатальная ошибка в будущем).