Реалізація синхронізації даних через iCloud

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація синхронізації даних через iCloud
Середній
~3-5 днів
Часті запитання

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

Етапи розробки

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

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація синхронізації даних через iCloud

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

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: Public, Private, Shared Database

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

  • Private Database — дані користувача, видні тільки йому. Витрачають iCloud-квоту користувача, не вашу.
  • 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
            )
        }
    }
}

CKSubscription: push-сповіщення при зміні

Коли користувач змінює дані на iPad, iPhone повинен дізнатися про це немедленно. 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)
}

// В AppDelegate / UNUserNotificationCenterDelegate
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
}

Silent push будить додаток у фоне (якщо дозволено Background App Refresh) та додаток підтягує зміни.

CKFetchRecordZoneChangesOperation: ефективна дельта-синхронізація

Запрашувати всі записи при кожній синхронізації — неефективно. 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)
}

Без serverChangeToken завантажуєте все. З токеном — тільки дельту.

Типічні проблеми

CKError.accountTemporarilyUnavailable. Користувач вийшов з iCloud або вимкнув синхронізацію для додатку. Потрібна обробка — не крешитися, а запропонувати входити або працювати тільки локально.

Network quota exceeded. Занадто часті запити до CloudKit. Apple обмежує частоту. Використовуйте subscriptions + delta sync замість polling.

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

Реалізація синхронізації через CloudKit з CKSubscription, дельта-синхронізацією та обробкою конфліктів: 2–4 тижні. Вартість рассчитывается індивідуально.