הערה: כאשר משתמש מחליף אייפון, נתוני האפליקציה אמורים להופיע במכשיר החדש באופן אוטומטי. אחרת, אתה מאבד את הלקוח. אנו משלבים סנכרון 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
)
}
}
} כיצד להגדיר סנכרון דלתות: מדריך שלב אחר שלב
- צור מנוי לשינויי רשומות (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) } - יישם טיפול בהודעות דחיפה ב-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 } - השתמש ב-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







