Налаштування Core Data в iOS-додатку
Core Data — не просто обгортка над SQLite. Це граф об'єктів з ленивою завантаженням, кешуванням, відстеженням змін та можливістю синхронізації через CloudKit. При правильному налаштуванні він прискорює роботу з локальними даними. При неправильному — викликає дедлоки та краш на NSFetchedResultsController.
Налаштування стеку
Починаючи з iOS 10, рекомендований спосіб — NSPersistentContainer. Інкапсулює NSManagedObjectModel, NSPersistentStoreCoordinator та основний NSManagedObjectContext.
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "DataModel")
container.loadPersistentStores { _, error in
if let error { fatalError("Core Data store failed: \(error)") }
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
automaticallyMergesChangesFromParent = true — критично. Без цього зміни, збережені в background context, не попадають автоматично в viewContext, та NSFetchedResultsController не оновлює UI.
Багатопоточність — головна ловушка
NSManagedObject не thread-safe. Передавати об'єкт між потоками неможливо — тільки objectID через NSManagedObjectID. У background context отримуємо копію об'єкта:
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.perform {
let objectInBg = backgroundContext.object(with: objectID)
// змінюємо objectInBg
try? backgroundContext.save()
}
Найчастіший крах: EXC_BAD_ACCESS або NSInternalInconsistencyException при доступі до NSManagedObject не у його потоці. Instruments → Core Data template показує, де це відбувається.
performAndWait vs perform. perform — асинхронний, performAndWait — синхронний та може викликати дедлок, якщо викликати з main thread з очіканням background context, який у свою чергу чекає main. Використовуємо perform для фонового зберігання.
NSFetchedResultsController та diffable data source
NSFetchedResultsController відстежує зміни в Core Data та сповіщає делегата. Зв'язка з UICollectionViewDiffableDataSource працює через controllerDidChangeContent:
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = NSDiffableDataSourceSnapshot<Section, NSManagedObjectID>()
snapshot.appendSections([.main])
snapshot.appendItems(controller.fetchedObjects?.map(\.objectID) ?? [])
dataSource.apply(snapshot, animatingDifferences: true)
}
Використовуємо objectID у snapshot, не сам NSManagedObject — інакше diffable source не може коректно порівнювати об'єкти.
Міграції
При зміні моделі даних потрібна міграція. Легка міграція (NSInferMappingModelAutomatically) працює для додавання/видалення атрибутів. Для переіменувань, зміни типів — кастомна migration policy через NSEntityMigrationPolicy. Без правильної міграції loadPersistentStores повертає NSMigrationError, та додаток не запуститься.
У конфігурацію додаємо:
container.persistentStoreDescriptions.first?.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions.first?.shouldInferMappingModelAutomatically = true
CloudKit синхронізація
NSPersistentCloudKitContainer замість NSPersistentContainer включає синхронізацію через iCloud CloudKit. Вимагає: iCloud Entitlement, CloudKit capability у Xcode, модель без деяких типів атрибутів (Binary Data з External Storage не синхронізується автоматично).
Конфлікти при синхронізації вирішуються через mergePolicy — NSMergeByPropertyObjectTrumpMergePolicy зазвичай правильний вибір.
Що входить у роботу
- Створення
.xcdatamodeldз entity та relationships - Налаштування
NSPersistentContainerз правильними параметрами контексту - Background context для імпорту та запису даних
-
NSFetchedResultsControllerдля відображення даних у UI - Стратегія міграції для майбутніх змін моделі
- Опціонально: CloudKit синхронізація
Термін
Базовий стек з однією-двома сутностями та NSFetchedResultsController: 1 день. Зі складною моделлю, міграціями, background sync та CloudKit інтеграцією: 2–3 дні. Вартість розраховується після аналізу вимог до даних.







