#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 (как показано выше). Без этого вы получите предупреждение (возможная фатальная ошибка в будущем).