Ana Sayfa / Blog / CloudKit ile offline-first uygulama nasıl kurulur?

CloudKit ile offline-first uygulama nasıl kurulur?

Kullanıcı internet yokken de çalışan, geri bağlandığında otomatik sync olan bir app kurmak. CloudKit ile 2 projede öğrendiklerim.

Dentii ve Snoozio’da kullanıcı verilerini CloudKit ile sync ediyorum. İkisi de offline-first çalışmak zorunda; kullanıcı uçakta, metroda, wifi’sız yerlerde uygulamayı kullanmaya devam ediyor. Bu iki app’te öğrendiklerim var, bu yazıda paylaşacağım.

Offline-first CloudKit kurgusu 4 temel kavramdan oluşuyor: local cache, sync engine, conflict resolution, error handling. Hepsini sırayla anlatacağım.

1. Local cache zorunlu

CloudKit tek başına offline-first değil. CKRecord’a erişim network gerektiriyor. Offline için her şeyi local’de de tutmalısın.

iOS 17 öncesi: CoreData + CloudKit mirror (NSPersistentCloudKitContainer)
iOS 17+: SwiftData + CloudKit (isCloudKitEnabled: true)

Her iki yaklaşım da local storage’ı source-of-truth olarak kullanıyor. App kapalıyken bile tüm veriler cihazda. CloudKit network üzerinden bu veriyi diğer cihazlarla sync ediyor.

Kritik: Local cache ile CloudKit schema’sı birebir aynı olmalı. Field ekledin local’de? CloudKit dashboard’unda da eklemen gerek. Aksi halde sync sessizce bozuluyor.

2. Sync engine setup

CloudKit’te 3 sync mekanizması var:

CKFetchChanges: Son sync’ten sonra değişen kayıtları çek. Push notification ile tetikleniyor.

CKSubscription: Record değişikliklerinde silent push notification alıyorsun. App’i background’da uyandırıp sync tetikliyor.

NSPersistentCloudKitContainer (Core Data): Apple bütün yukarıdakini otomatik yapıyor. Sadece container’ı doğru konfigüre ediyorsun.

Ben 3’ünü de kullandım. Tavsiyem: mümkünse Apple’ın otomatik sync’ine güven. Manual sync’i ancak çok özel ihtiyaçlar için uygula.

3. Conflict resolution stratejisi

Offline’da kullanıcı cihaz 1’de bir kayıt değiştiriyor. Aynı anda cihaz 2’de de değiştiriyor. Sync olduğunda 2 farklı versiyon var, hangi kazanacak?

CloudKit default olarak “last write wins” stratejisi kullanıyor. Yani hangi cihaz daha sonra sync ettiyse o kazanıyor. Bu genelde sorun yaratıyor; kullanıcının eski cihazdaki değişiklikleri kayboluyor.

Daha iyi stratejiler:

Field-level merge: Her field’ın timestamp’ini tut, her field için son yazan kazanır. Karmaşık ama data kaybını minimize ediyor.

User-prompted resolution: Çakışma olduğunda kullanıcıya “A versiyonu mu, B versiyonu mu” sor. Kritik veri için (financial, medical) uygun.

Operational transformation: Değişiklikleri operation olarak kaydedip her iki tarafı da uygula. Google Docs stil collaborative editing için gerekir, basit app’ler için aşırı.

Dentii’de field-level merge kullandım. Her brushing session timestamp’li, cihazlar arası merge otomatik. Snoozio’da last-write-wins yeterli çünkü sleep data tek cihazdan geliyor genelde.

4. Error handling

CloudKit’in kendine özgü hata kodları var:

  • Quota exceeded: Kullanıcının iCloud storage’ı dolu. App’in iCloud storage talebi reddedildi.
  • Network unavailable: Offline durum. Sync queue’ya al, tekrar dene.
  • Server conflict: Record version mismatch. Yeniden fetch edip merge et.
  • Record not found: Silinen bir kaydı güncellemeye çalışıyorsun. Local’den de sil.
  • Permission failure: Kullanıcı iCloud’a sign in olmamış veya disable etmiş. UI’da uyarı göster.

Her biri için ayrı bir recovery path kurmak gerekiyor. En sık karşılaştığım:

switch error.code {
case .networkUnavailable, .networkFailure:
    // Retry queue'ya ekle
case .serverRecordChanged:
    // Conflict resolution
case .quotaExceeded:
    // Kullanıcıya uyarı
case .notAuthenticated:
    // iCloud sign-in prompt
default:
    // Log, retry with backoff
}

5. Background sync setup

App background’dayken de sync olması için:

BGTaskScheduler (iOS 13+) ile periodic sync register et:

BGTaskScheduler.shared.register(
    forTaskWithIdentifier: "com.app.sync",
    using: nil
) { task in
    handleBackgroundSync(task: task as! BGAppRefreshTask)
}

Apple günde 2-3 kere background execution veriyor. Tüm sync bir shot’ta yapılmıyor, gerçekçi bekleyişler kur.

Ayrıca CKSubscription ile silent push notification gönderiliyor. App wake up olup sync yapıyor. Bu en güvenilir background sync yöntemi.

6. Test etmek

CloudKit test’i zor çünkü production ve development environment’ları ayrı. Test stratejim:

  1. Development environment’da iCloud testing. iCloud test hesapları oluştur, simulator’da sign in.
  2. Conflict simulation. İki simulator’da aynı hesap, her ikisinde de offline değişiklik yap, sonra sync.
  3. Network degradation. Network Link Conditioner ile slow/flaky connection simülasyonu.
  4. Production smoke test. Release öncesi production environment’da 2 cihazda birkaç saat test.

Kullanıcı deneyimi notları

Offline-first app tasarlarken UX detaylar önemli:

Sync indicator göster. Kullanıcı “datam sync oluyor mu” sorusunun cevabını görsel olarak alabilmeli.

Last sync time göster. “Son sync: 2 dakika önce” gibi. Belirsizlik azalıyor.

Manual refresh option. Pull-to-refresh veya sync button. Otomatik sync’e güvensizlik varsa kullanıcı manuel tetiklesin.

Offline state açıkça söyle. “Çevrimdışı, veriler cihazınızda” gibi. Kullanıcı verilerini kaybedeceğini sanmasın.

Sonuç

Offline-first CloudKit kurgusu 1-2 haftalık iş, ama değer veren bir investment. Kullanıcı deneyimi uçak moduna geçse bile kesintisiz. Sync UX’i doğru yapılırsa kullanıcı CloudKit’in var olduğunu bile hissetmiyor, sadece ‘data işliyor’ diye görüyor.

En büyük hata local cache’i atlayıp her şeyi network üzerinden yapmak. Offline-first mentalitesiyle başla, network sadece optimization olarak gelsin. Ters sıraya başlarsan, offline support’u sonradan eklemek 3x daha zor.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç