В этой статье я бы хотел еще немного раскрыть возможности Core Data. Данная статья является продолжением. Я очень надеюсь что у меня получится донести мысль о том, что Core Data не одна из множества реляционных баз данных, а очень мощный инструмент который сможет стать неотъемлемым оружием в арсенале любого уважающего себя IOS-разработчика. 
Ну что ж, начнем!
Сегодня мы посмотрим на работу с двумя NSManageObjectContext и NSFetchedResultsController.
Если ваше приложение активно задействует какой-нибудь API через которое оно получает данные, и вы хотите эти данные где-то сохранять, то отличный выбор — Core Data. Какие у вас могут появиться проблемы с этим? Первое и самое очевидное — количество данных будет так велико, что при попытке сохранить их, наше приложение зависнет на какое-то время, что скажется впечатлении пользователя. Обычным GCD тут не обойтись так как независимо, наш Core Data Stack, о котором говорилось тут, работает синхронно и при любом обращении к нашему NSManageObjectContext нам придется ждать до конца выполнения цикла.
Но тут на помощь нам приходит приватный NSManageObjectContext который работает в background потоке. Для того чтобы его вызвать требуется сначала обратиться к нашему NSPersistentContainer.
Инициализация будет выглядеть следующим образом:
Тут вы указываем политику слияния наших NSManageObjectContext. Тут мы явно указываем как должна себя повести Core Data в случае конфликта данных.
Варианты MergePolicy:
AutomaticallyMergesChangesFromParent — говорит о том будет ли наш контекст автоматически объединять данные
После чего создаем новый контекст:
Теперь у нас имеется два NSManageObjectContext. Первый служит для работы с UI и работает на главном потоке, а второй имеет privateQueueConcurrencyType для работы в фоне.
Мы будем использовать его для скачивания данных.
Тут мы создаем наше Entity и далее можем присвоить ему необходимые свойства, после чего вызываем метод сохранения, выглядит он следующим образом:
Есть 2 метода на выбор:
NSFetchedResultsController — контроллер который выполняет определенные запросы и показывает необходимые данные пользователю.
NSFetchedResultsController имеет очень большое количество конфигураций, разберем парочку из них:
На данный момент мы имеем NSFetchedResultsController который должен отобразить наши данные в таблице. Для того чтобы обновить данные нужно вызвать метод делегата:
 
Данный метод делегата срабатывает когда у нас происходят какие-то изменения в нашем контексте. В данной реализации это происходит после того как мы сохраняем данные в privateContext. В этот момент у нас срабатывает метод делегата и данные обновляются.
Всего пару действий и Core Data из обычной базы данных превращается в мощное оружие любого IOS разработчика.
Happy Coding!
			  Ну что ж, начнем!
Сегодня мы посмотрим на работу с двумя 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 имеет очень большое количество конфигураций, разберем парочку из них:
FetchLimit
FetchLimit — указывает лимит по выборке объектов 
FetchOffset
FetchOffset — указывает отступ. Если указать значение 2, то показываться элементы будут со второго элемента.
FetchBatchSize
FetchBatchSize — указывает какое количество элементов будет подгружено за раз. Если вы отображаете элементы в Table/CollectionView и у вас за раз показывается всего 5 элементов, в целях производительности лучше подгружать не больше 7 элементов за раз, чем брать сразу все данные и держать их в памяти.
FetchBatchSize
FetchBatchSize — устанавливает лимит на обьем который весит объект и не подгрузит его если объект превышает этот лимит.
SortDescriptor
SortDescriptor — указывает по какому ключу следует отсортировать запрос, а так же по возрастанию либо наоборот.
ReturnsObjectsAsFaults
ReturnsObjectsAsFaults — указывает, что наши значения могут придти пустыми, и когда мы к ним обратимся, они будут подгружены с нашего PersistentStore которые в данный момент находятся там в виде RawCache
На данный момент мы имеем 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!
          
 
varton86
Спасибо за статью.
В методе делегата все-таки лучше обновлять те данные, которые изменились, а не всю collectionView. Например, так: collectionView.reloadRows(at: [indexPath!], with: .automatic)
Еще можно задействовать методы делегата controllerWillChangeContent и controllerDidChangeContent, в которых выполнить beginUpdates и endUpdates.
Также можно, в зависимости от NSFetchedResultsChangeType, выполнить insertRows, deleteRows или reloadRows.
Maksimilliano Автор
Спасибо за комментарий. Да, верно. Но в случае когда получаем большой объем данных, как в примере, можно и просто reloadData вызвать.
Методы делегата — несомненно. Можно отлавливать изменения над конкретным объектом и производить какие-нибудь манипуляции!