Синхронізація iCloud: CloudKit та NSUbiquitousKeyValueStore

Зазначимо: коли користувач змінює iPhone, дані додатку повинні з'явитися на новому пристрої автоматично. Інакше — втрата клієнта. Ми інтегруємо синхронізацію iCloud через три механізми: NSUbiquitousKeyValueStore, CloudKit та iCloud Documents (UIDocument). Кожен вирішує своє завдання, але для складно

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Синхронізація iCloud: CloudKit та NSUbiquitousKeyValueStore
Середній
~3-5 днів

Наші компетенції:

Часті запитання

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    894
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    782
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1216
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1079
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    1002
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    597

Зазначимо: коли користувач змінює iPhone, дані додатку повинні з'явитися на новому пристрої автоматично. Інакше — втрата клієнта. Ми інтегруємо синхронізацію iCloud через три механізми: NSUbiquitousKeyValueStore, CloudKit та iCloud Documents (UIDocument). Кожен вирішує своє завдання, але для складної синхронізації з мінімальними затримками найкраще підходить CloudKit. Синхронізація даних через iCloud — це не просто перенесення файлів, а складний процес з дельтами, конфліктами та обмеженнями сховища. Без правильної архітектури користувач втрачає прогрес, налаштування або нотатки при зміні пристрою.

Один із наших проєктів — додаток для нотаток з багатокористувацьким редагуванням. Перша версія використовувала повне вивантаження всіх записів при кожному запуску. Трафік перевищував 10 МБ на користувача на день. Перехід на дельта-синхронізацію з serverChangeToken скоротив передані дані до 1 МБ — зниження на 90%. Користувачі перестали скаржитися на повільне завантаження, а кількість запитів до CloudKit зменшилася в 10 разів.

Коли використовувати NSUbiquitousKeyValueStore, CloudKit або iCloud Documents?

Критерій NSUbiquitousKeyValueStore CloudKit iCloud Documents
Максимальний об'єм 1 МБ, 1024 ключа 10 МБ/користувач (безкоштовно), дод. 1 ГБ за $0.99/міс Обмеження вільного місця iCloud
Тип даних Налаштування, прості конфігурації Довільні записи (нотатки, прогрес, списки) Файли, зображення, документи
Синхронізація Автоматична, без коду Вимагає підписок і дельта-оновлень Автоматична через UIDocument
Підтримка конфліктів Останній запис перемагає Ручний merge для кастомних зон Версіонування (UIDocument)

Apple надає 10 МБ безкоштовного сховища CloudKit на користувача, додатковий 1 ГБ коштує $0.99 на місяць. Економія на трафіку від дельта-синхронізації може скоротити витрати на запити в 5 разів.

NSUbiquitousKeyValueStore

Найпростіший варіант — для невеликих конфігураційних даних. Ліміт 1 МБ на все сховище, 1024 ключі, до 256 КБ на ключ. Синхронізується автоматично, без коду синхронізації.

let store = NSUbiquitousKeyValueStore.default // Запис store.set(userId, forKey: "lastUserId") store.set(["theme": "dark", "fontSize": 16], forKey: "userSettings") store.synchronize() // запрошує негайну синхронізацію, не гарантує // Читання let theme = store.string(forKey: "userSettings.theme") ?? "light" // Підписка на зміни з інших пристроїв NotificationCenter.default.addObserver( self, selector: #selector(iCloudDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default ) @objc func iCloudDidChange(_ notification: Notification) { guard let keys = notification.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] else { return } // Оновлюємо локальний стан для змінених ключів keys.forEach { updateLocalState(forKey: $0) } } 

Ідеально для налаштувань. Для прогресу гри, нотаток, файлів — CloudKit.

Чому обирають CloudKit для складної синхронізації?

CloudKit — повноцінна база даних в iCloud. Три типи сховищ:

Тип бази Видимість Витрата квоти Приклад використання
Private Database Тільки користувач Користувацька Особисті нотатки, налаштування
Public Database Всі користувачі Розробника Контент додатку, рейтинги
Shared Database Вибрані користувачі Користувацька Спільні списки, редагування
import CloudKit class CloudKitManager { let container = CKContainer(identifier: "iCloud.com.company.appname") var privateDB: CKDatabase { container.privateCloudDatabase } // Збереження нотатки func saveNote(_ note: Note) async throws { let record = CKRecord(recordType: "Note", recordID: CKRecord.ID(recordName: note.id)) record["title"] = note.title as CKRecordValue record["content"] = note.content as CKRecordValue record["modifiedAt"] = Date() as CKRecordValue record["isPinned"] = note.isPinned as CKRecordValue let savedRecord = try await privateDB.save(record) print("Saved: \(savedRecord.recordID.recordName)") } // Завантаження всіх нотаток func fetchAllNotes() async throws -> [Note] { let predicate = NSPredicate(value: true) let query = CKQuery(recordType: "Note", predicate: predicate) query.sortDescriptors = [NSSortDescriptor(key: "modifiedAt", ascending: false)] let (results, _) = try await privateDB.records(matching: query) return results.compactMap { (_, result) in guard let record = try? result.get() else { return nil } return Note( id: record.recordID.recordName, title: record["title"] as? String ?? "", content: record["content"] as? String ?? "", isPinned: record["isPinned"] as? Bool ?? false ) } } } 

Як налаштувати дельта-синхронізацію: покрокова інструкція

  1. Створіть підписку на зміни записів (CKQuerySubscription) — це дозволить отримувати silent push при кожній зміні.
    func setupSubscription() async throws { let predicate = NSPredicate(value: true) let subscription = CKQuerySubscription( recordType: "Note", predicate: predicate, subscriptionID: "notes-changes", options: [.firesOnRecordCreation, .firesOnRecordUpdate, .firesOnRecordDeletion] ) let notificationInfo = CKSubscription.NotificationInfo() notificationInfo.shouldSendContentAvailable = true // silent push subscription.notificationInfo = notificationInfo try await privateDB.save(subscription) } 
  2. Реалізуйте обробку push-сповіщень в AppDelegate — при отриманні silent push викликайте метод fetchChanges().
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult { let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) if notification?.containerIdentifier == "iCloud.com.company.appname" { await cloudKitManager.fetchChanges() return .newData } return .noData } 
  3. Використовуйте CKFetchRecordZoneChangesOperation з serverChangeToken — завантажуйте лише змінені записи.
    func fetchChanges() async throws { let zone = CKRecordZone(zoneName: "NotesZone") var config = CKFetchRecordZoneChangesOperation.ZoneConfiguration() config.previousServerChangeToken = UserDefaults.standard .data(forKey: "notesZoneChangeToken") .flatMap { try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, from: $0) } let operation = CKFetchRecordZoneChangesOperation( recordZoneIDs: [zone.zoneID], configurationsByRecordZoneID: [zone.zoneID: config] ) operation.recordWasChangedBlock = { _, result in guard let record = try? result.get() else { return } Task { await self.localStore.upsert(record) } } operation.recordWithIDWasDeletedBlock = { recordID, _ in Task { await self.localStore.delete(id: recordID.recordName) } } operation.recordZoneFetchResultBlock = { _, result in guard case .success(let info) = result else { return } // Зберігаємо токен для наступної дельта-синхронізації if let tokenData = try? NSKeyedArchiver.archivedData( withRootObject: info.newServerChangeToken, requiringSecureCoding: true) { UserDefaults.standard.set(tokenData, forKey: "notesZoneChangeToken") } } privateDB.add(operation) } 

Як уникнути конфліктів при одночасному редагуванні?

CloudKit не вирішує конфлікти автоматично для Custom Zones. При save по існуючому recordID, якщо recordChangeTag не збігається — помилка serverRecordChanged. Потрібен ручний merge. Ми використовуємо стратегію last-writer-wins або тристороннє злиття. Ось обробник конфлікту:

// В блоці save з CKModifyRecordsOperation operation.perRecordSaveBlock = { recordID, saveResult in if case .failure(let error) = saveResult { if let ckError = error as? CKError, ckError.code == .serverRecordChanged { let serverRecord = ckError.userInfo[CKRecordChangedErrorServerRecordKey] as! CKRecord let clientRecord = ckError.userInfo[CKRecordChangedErrorClientRecordKey] as! CKRecord // Вирішуємо конфлікт: беремо останню версію serverRecord["modifiedAt"] = Date() operation.recordsToSave = [serverRecord] } } } 
Типові проблеми при синхронізації CloudKit
  • CKError.accountTemporarilyUnavailable: користувач вийшов з iCloud або вимкнув синхронізацію для додатка. Обробляємо — не крэшим, пропонуємо увійти або працювати локально.
  • Network quota exceeded: занадто часті запити до CloudKit. Використовуємо subscriptions + delta sync замість polling.
  • Конфлікти при одночасному редагуванні. Вирішуємо ручним merge, як описано вище.

Що входить в роботу

Етап Тривалість Результат
Аналіз вимог і вибір архітектури 1–2 дні Технічне завдання, схема даних
Проектування бази CloudKit 1–2 дні Моделі записів, індекси, підписки
Реалізація синхронізації (iOS) 5–10 днів Код з інтеграцією CloudKit, обробкою конфліктів
Налаштування push-сповіщень 1 день Silent push для фонової синхронізації
Тестування на декількох пристроях 2–3 дні Навантажувальне тестування, виправлення багів
Деплой та моніторинг 1 день Доступ до консолі CloudKit, дашборд помилок

Терміни: від 2 до 4 тижнів. Вартість розраховується індивідуально. Отримайте консультацію – ми оцінимо ваш проект за один день. Зв'яжіться з нами, щоб обговорити деталі.

Ми займаємося iOS-розробкою понад 10 років, реалізували 30+ проєктів із синхронізацією через CloudKit. Гарантуємо безшовну синхронізацію між пристроями протягом 24 годин після деплою. Замовте інтеграцію CloudKit вже сьогодні та позбудьтеся проблем із синхронізацією.

Apple CloudKit Documentation