Стартуем с Core Data! Сложное простыми словами [Часть 2]

в 11:21, , рубрики: apple, core data, objc-c, objcetive-c, swift, xcode

В этой статье я бы хотел еще немного раскрыть возможности 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:

  1. NSRollbackMergePolicy — В случае появления конфликта, отбрасывает все изменения до его появления
  2. NSOverwriteMergePolicy — Сохранит новые значения независимо от данных
  3. NSMergeByPropertyStoreTrumpMergePolicy — Сохраняет измененные объекты свойство за свойством, в данном случае преобладать будут сохраненные данные
  4. 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!

Автор: Максим

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js