סנכרון iCloud: שילוב CloudKit ו-NSUbiquitousKeyValueStore

כאשר משתמש מחליף מכשירים, נתוני האפליקציה חייבים לעבור אוטומטית — אחרת, תאבדו את הלקוח. אנו משלבים סנכרון iCloud באמצעות CloudKit ו-NSUbiquitousKeyValueStore, ומספקים את הפרויקט במפתח מלא עם עדכוני דלתא וטיפול בקונפליקטים. הצוות שלנו מבטיח סנכרון אמין ותמיכה מתמשכת, תוך שמירה על נתונים מעודכנים בכל המכשירים ללא תקלות.

פיתוח ותמיכה בכל סוגי היישומים הניידים:

יישומים ניידים למידע ובידור
אפליקציות חדשות, משחקים, מדריכים, קטלוגים מקוונים, אפליקציות מזג אוויר, אפליקציות כושר ובריאות, אפליקציות נסיעות, אפליקציות חינוכיות, רשתות חברתיות ומסנג'רים, חידונים, בלוגים ופודקאסטים, פורומים, אגרגטורים
יישומים ניידים למסחר אלקטרוני
חנויות מקוונות, אפליקציות B2B, מרקטפלייסים, בורסות מקוונות, שירותי קאשבק, בורסות, פלטפורמות דרופשיפינג, תוכניות נאמנות, משלוחי מזון ומוצרים, מערכות תשלום.
יישומים ניידים לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, ניהול פרויקטים, כלים לצוותי מכירות, ניהול פיננסי, ניהול ייצור, ניהול לוגיסטיקה ומשלוחים, ניהול משאבי אנוש, מערכות ניטור נתונים
יישומים ניידים לשירותים אלקטרוניים
פלטפורמות מודעות מסווגות, בתי ספר מקוונים, בתי קולנוע מקוונים, פלטפורמות שירות אלקטרוניות, פלטפורמות קאשבק, אירוח וידאו, פורטלים נושאיים, פלטפורמות הזמנות ותזמון מקוונות, פלטפורמות מסחר מקוונות

אלה הם רק חלק מסוגי היישומים הניידים שאנו עובדים איתם, ולכל אחד מהם עשויים להיות מאפיינים ופונקציונליות ספציפיים משלו, המותאמים לצרכים ולמטרות הספציפיות של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 1734 השירותים
סנכרון iCloud: שילוב CloudKit ו-NSUbiquitousKeyValueStore
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית מובייל עבור FEEDME
    פיתוח אפליקציית מובייל עבור FEEDME
    971
  • פיתוח אפליקציית מובייל עבור XOOMER
    פיתוח אפליקציית מובייל עבור XOOMER
    869
  • פיתוח אפליקציית מובייל עבור RHL
    פיתוח אפליקציית מובייל עבור RHL
    1289
  • פיתוח אפליקציית מובייל עבור ZIPPY
    פיתוח אפליקציית מובייל עבור ZIPPY
    1143
  • פיתוח אפליקציית מובייל עבור Affhome
    פיתוח אפליקציית מובייל עבור Affhome
    1076
  • פיתוח אפליקציית מובייל עבור חברת FLAVORS
    פיתוח אפליקציית מובייל עבור חברת FLAVORS
    677

הערה: כאשר משתמש מחליף אייפון, נתוני האפליקציה אמורים להופיע במכשיר החדש באופן אוטומטי. אחרת, אתה מאבד את הלקוח. אנו משלבים סנכרון iCloud באמצעות שלושה מנגנונים: NSUbiquitousKeyValueStore, CloudKit, ו-iCloud Documents (UIDocument). כל אחד מהם פותר משימה משלו, אך לסנכרון מורכב עם זמן השהייה מינימלי, CloudKit הוא הבחירה הטובה ביותר. סנכרון נתונים דרך iCloud אינו רק העברת קבצים—זהו תהליך מורכב עם דלתות, התנגשויות ומגבלות אחסון. ללא ארכיטקטורה נכונה, המשתמש מאבד התקדמות, הגדרות או הערות בעת מעבר בין מכשירים.

אחד הפרויקטים שלנו הוא אפליקציית רישום הערות עם עריכה מרובת משתמשים. הגרסה הראשונה העלתה את כל ההערות במלואן בכל השקה. התעבורה עלתה על 10 MB למשתמש ליום. המעבר לסנכרון דלתות עם serverChangeToken הפחית את הנתונים המועברים ל-1 MB—הפחתה של 90%. המשתמשים הפסיקו להתלונן על טעינה איטית, ומספר הבקשות ל-CloudKit ירד פי עשרה.

מתי להשתמש ב-NSUbiquitousKeyValueStore, CloudKit, או iCloud Documents?

קריטריון NSUbiquitousKeyValueStore CloudKit iCloud Documents
נפח מקסימלי 1 MB, 1024 מפתחות 10 MB למשתמש (חינם), 1 GB נוסף תמורת $0.99 לחודש מוגבל על ידי שטח פנוי ב-iCloud
סוג נתונים הגדרות, תצורות פשוטות רשומות שרירותיות (הערות, התקדמות, רשימות) קבצים, תמונות, מסמכים
סנכרון אוטומטי, ללא קוד דורש מנויים ועדכוני דלתות אוטומטי דרך UIDocument
תמיכה בהתנגשויות הכתיבה האחרונה מנצחת מיזוג ידני עבור אזורים מותאמים אישית גרסאות (UIDocument)

Apple מספקת 10 MB אחסון CloudKit חינם למשתמש, 1 GB נוסף עולה $0.99 לחודש. חיסכון בתעבורה מסנכרון דלתות יכול להפחית את עלויות השאילתות פי 5.

NSUbiquitousKeyValueStore

האפשרות הפשוטה ביותר—לנתוני תצורה קטנים. מגבלה: 1 MB אחסון כולל, 1024 מפתחות, עד 256 KB למפתח. מסונכרן אוטומטית, ללא צורך בקוד סנכרון.

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. שלושה סוגי אחסון:

סוג מסד נתונים נראות צריכת מכסה דוגמה לשימוש
מסד נתונים פרטי רק המשתמש של המשתמש הערות אישיות, הגדרות
מסד נתונים ציבורי כל המשתמשים של המפתח תוכן אפליקציה, דירוגים
מסד נתונים משותף משתמשים נבחרים של המשתמש רשימות שיתופיות, עריכה
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) — זה מאפשר דחיפה שקטה בכל שינוי.
    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. יישם טיפול בהודעות דחיפה ב-AppDelegate — בעת קבלת דחיפה שקטה, קרא ל-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 אינו פותר התנגשויות אוטומטית עבור אזורים מותאמים אישית. בעת שמירה ל-recordID קיים, אם ה-recordChangeTag אינו תואם, אתה מקבל שגיאת serverRecordChanged. נדרש מיזוג ידני. אנו משתמשים באסטרטגיית "הכתיבה האחרונה מנצחת" או מיזוג תלת-כיווני. הנה מטפל התנגשויות:

// В блоке 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 או השבית סנכרון לאפליקציה. טפל בזה בחן—אל תקרוס, הצע התחברות או עבודה מקומית.
  • חריגה ממכסת רשת: בקשות תכופות מדי ל-CloudKit. השתמש במנויים + סנכרון דלתות במקום שאילתות חוזרות.
  • התנגשויות בעת עריכה בו-זמנית. פתור עם מיזוג ידני כמתואר לעיל.

מה כלול בעבודה

שלב משך תוצאה
ניתוח דרישות ובחירת ארכיטקטורה 1–2 ימים מפרט טכני, סכמת נתונים
עיצוב מסד נתונים ב-CloudKit 1–2 ימים מודלי רשומות, אינדקסים, מנויים
יישום סנכרון (iOS) 5–10 ימים קוד עם אינטגרציית CloudKit, טיפול בהתנגשויות
הגדרת הודעות דחיפה יום אחד דחיפה שקטה לסנכרון ברקע
בדיקות על מספר מכשירים 2–3 ימים בדיקות עומס, תיקוני באגים
פריסה וניטור יום אחד גישה לקונסולת CloudKit, לוח שגיאות

ציר זמן: בין 2 ל-4 שבועות. העלות מחושבת באופן אישי. קבל ייעוץ—נעריך את הפרויקט שלך ביום אחד. צור קשר כדי לדון בפרטים.

אנו עוסקים בפיתוח iOS למעלה מ-10 שנים, ביצענו 30+ פרויקטים עם סנכרון CloudKit. אנו מבטיחים סנכרון חלק בין מכשירים תוך 24 שעות לאחר הפריסה. הזמן אינטגרציית CloudKit היום והיפטר מבעיות סנכרון.

תיעוד Apple CloudKit