В этой статье я бы хотел еще немного раскрыть возможности Core Data. Данная статья является продолжением. Я очень надеюсь что у меня получится донести мысль о том, что Core Data не одна из множества реляционных баз данных, а очень мощный инструмент который сможет стать неотъемлемым оружием в арсенале любого уважающего себя IOS-разработчика.
Ну что ж, начнем!
Сегодня мы посмотрим на работу с двумя NSManageObjectContext и NSFetchedResultsController.
Два NSManageObjectContext и зачем это надо?
Если ваше приложение активно задействует какой-нибудь API через которое оно получает данные, и вы хотите эти данные где-то сохранять, то отличный выбор — Core Data. Какие у вас могут появиться проблемы с этим? Первое и самое очевидное — количество данных будет так велико, что при попытке сохранить их, наше приложение зависнет на какое-то время, что скажется впечатлении пользователя. Обычным GCD тут не обойтись так как независимо, наш Core Data Stack, о котором говорилось тут, работает синхронно и при любом обращении к нашему NSManageObjectContext нам придется ждать до конца выполнения цикла.
Но тут на помощь нам приходит приватный NSManageObjectContext который работает в background потоке. Для того чтобы его вызвать требуется сначала обратиться к нашему NSPersistentContainer.
Инициализация будет выглядеть следующим образом:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Preset")
container.loadPersistentStores { (persistent, error) in
if let error = error {
fatalError("Error: " + error.localizedDescription)
}
}
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
MergePolicy
Тут вы указываем политику слияния наших NSManageObjectContext. Тут мы явно указываем как должна себя повести Core Data в случае конфликта данных.
Варианты MergePolicy:
- NSRollbackMergePolicy — В случае появления конфликта, отбрасывает все изменения до его появления
- NSOverwriteMergePolicy — Сохранит новые значения независимо от данных
- NSMergeByPropertyStoreTrumpMergePolicy — Сохраняет измененные объекты свойство за свойством, в данном случае преобладать будут сохраненные данные
- NSMergeByPropertyObjectTrumpMergePolicy — Сохраняет измененные объекты свойство за свойством, в данном случае преобладать будут новые данные
AutomaticallyMergesChangesFromParent — говорит о том будет ли наш контекст автоматически объединять данные
После чего создаем новый контекст:
let context = persistentContainer.viewContext
let context = persistentContainer.newBackgroundContext()
Теперь у нас имеется два NSManageObjectContext. Первый служит для работы с UI и работает на главном потоке, а второй имеет privateQueueConcurrencyType для работы в фоне.
Мы будем использовать его для скачивания данных.
let object = NSEntityDescription.insertNewObject(forEntityName: "Name", into: context)
saveChanges(with: context)
Тут мы создаем наше Entity и далее можем присвоить ему необходимые свойства, после чего вызываем метод сохранения, выглядит он следующим образом:
func saveChanges(with context: NSManagedObjectContext) {
context.performAndWait {
if context.hasChanges {
do {
try context.save()
} catch {
context.rollback()
}
}
context.reset()
}
}
Есть 2 метода на выбор:
- performAndWait — выполняет действия на потоке контекста синхронно
- perform — выполняет действия на потоке контекста асинхронно
NSFetchedResultsController
NSFetchedResultsController — контроллер который выполняет определенные запросы и показывает необходимые данные пользователю.
lazy var fetchedResultsController: NSFetchedResultsController<Pack> = {
let fetchRequest = NSFetchRequest<Pack>(entityName:"Pack")
fetchRequest.fetchBatchSize = 20
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending:true)]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
do {
try controller.performFetch()
} catch {
print(error.localizedDescription)
}
return controller
}()
NSFetchedResultsController имеет очень большое количество конфигураций, разберем парочку из них:
На данный момент мы имеем NSFetchedResultsController который должен отобразить наши данные в таблице. Для того чтобы обновить данные нужно вызвать метод делегата:
extension ViewController: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
collectionView.reloadData()
}
}
Данный метод делегата срабатывает когда у нас происходят какие-то изменения в нашем контексте. В данной реализации это происходит после того как мы сохраняем данные в privateContext. В этот момент у нас срабатывает метод делегата и данные обновляются.
Всего пару действий и Core Data из обычной базы данных превращается в мощное оружие любого IOS разработчика.
Happy Coding!
Автор: Максим