CloudKit Integration
NSPersistentCloudKitContainer syncs Core Data with CloudKit, enabling seamless data synchronization across devices.
Setup
Basic Setup
import CoreData
import CloudKit
let container = NSPersistentCloudKitContainer(name: "Model")
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Failed to load store: \(error)")
}
}Configure CloudKit Container
In Xcode:
- Add CloudKit capability
- Select or create CloudKit container
- Enable "Use CloudKit" in Core Data model
Schema Design Limitations
CloudKit has restrictions Core Data doesn't:
Not Supported:
- Unique constraints on entities
Undefinedattribute typeObjectIDattribute type- Non-optional relationships (must be optional)
- Relationships without inverse
- Deny deletion rule
Supported:
- Adding new fields to record types
- Adding new record types
Important: Production schema is immutable. Plan carefully!
Schema Initialization
Development Environment
// First run initializes schema in Development
container.loadPersistentStores { description, error in
// Schema created automatically
}Promoting to Production
- Test thoroughly in Development
- Open CloudKit Dashboard
- Deploy schema to Production
- Cannot modify after deployment!
Monitoring Sync
Observe Events
NotificationCenter.default.addObserver(
self,
selector: #selector(storeDidChange),
name: NSPersistentCloudKitContainer.eventChangedNotification,
object: container
)
@objc func storeDidChange(_ notification: Notification) {
guard let event = notification.userInfo?[NSPersistentCloudKitContainer.eventNotificationUserInfoKey]
as? NSPersistentCloudKitContainer.Event else {
return
}
switch event.type {
case .setup:
print("Setup: \(event.succeeded ? "succeeded" : "failed")")
case .import:
print("Import: \(event.succeeded ? "succeeded" : "failed")")
case .export:
print("Export: \(event.succeeded ? "succeeded" : "failed")")
@unknown default:
break
}
if let error = event.error {
print("Error: \(error)")
}
}Testing Sync
func testSync() {
let expectation = XCTestExpectation(description: "Export")
// Create expectation for export
let observer = NotificationCenter.default.addObserver(
forName: NSPersistentCloudKitContainer.eventChangedNotification,
object: container,
queue: nil
) { notification in
guard let event = notification.userInfo?[NSPersistentCloudKitContainer.eventNotificationUserInfoKey]
as? NSPersistentCloudKitContainer.Event else {
return
}
if event.type == .export && event.endDate != nil {
expectation.fulfill()
}
}
// Make changes
let article = Article(context: container.viewContext)
article.name = "Test"
try? container.viewContext.save()
wait(for: [expectation], timeout: 60)
NotificationCenter.default.removeObserver(observer)
}Cross-Version Compatibility
Strategy 1: Incremental Fields
Add new fields, keep old ones:
// V1: name
// V2: name, subtitle (new)
// Old versions see records but not subtitleStrategy 2: Version Attribute
// Add version attribute
article.schemaVersion = 2
// Filter in fetch requests
fetchRequest.predicate = NSPredicate(format: "schemaVersion <= %d", currentVersion)Strategy 3: New Container
let options = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.example.app.v2"
)
let description = NSPersistentStoreDescription(url: storeURL)
description.cloudKitContainerOptions = optionsCaution: Large datasets take time to upload.
Debugging
System Logs
Monitor these processes:
- Application - Core Data activity
- dasd - Scheduling decisions
- cloudd - CloudKit operations
- apsd - Push notifications
Using log stream
# Application logs
log stream --predicate 'process == "YourApp"'
# CloudKit logs
log stream --predicate 'process == "cloudd" AND message CONTAINS "your.container.id"'
# Push notifications
log stream --predicate 'process == "apsd"'
# Scheduling
log stream --predicate 'process == "dasd" AND message CONTAINS "YourApp"'CloudKit Logging Profile
- Download from Apple Developer Portal
- Install on device
- Reboot device
- Reproduce issue
- Collect sysdiagnose
Collecting Diagnostics
sysdiagnose:
- iOS: Volume Up + Volume Down + Power (hold)
- macOS: Shift + Control + Option + Command + Period
Common Issues
Schema Mismatch
Problem: Local schema doesn't match CloudKit schema.
Solution:
- Delete app
- Reinstall
- Let schema reinitialize
Sync Not Working
Checklist:
- CloudKit capability enabled
- Signed in to iCloud
- Network connection available
- CloudKit container configured
- Schema initialized in Development
- Schema promoted to Production
Large Initial Sync
Problem: First sync takes too long.
Solutions:
- Use background fetch
- Show progress indicator
- Implement data generators for testing
Best Practices
- Test in Development first - Schema is mutable
- Plan schema carefully - Production is immutable
- Make relationships optional - Required by CloudKit
- Add inverse relationships - Required by CloudKit
- Version your data - For cross-version compatibility
- Monitor sync events - Detect and handle errors
- Test with multiple devices - Verify sync behavior
- Handle conflicts - Use appropriate merge policy
- Collect diagnostics - For debugging sync issues
- Consider data size - Large datasets take time to sync
Summary
- Use
NSPersistentCloudKitContainerfor CloudKit sync - Schema has limitations (optional relationships, no constraints)
- Production schema is immutable
- Monitor sync with event notifications
- Test thoroughly in Development before promoting
- Plan for cross-version compatibility
- Use system logs for debugging
- Collect sysdiagnose for complex issues