// In AppDelegate
private func logCloudKitStatus() {
let container = CKContainer(identifier: "iCloud.ro.xdev.USB-Meter")
container.accountStatus { status, error in
if let error {
print("CloudKit error: \(error.localizedDescription)")
return
}
switch status {
case .available:
print("✅ CloudKit available")
case .noAccount:
print("❌ No iCloud account")
case .restricted:
print("❌ CloudKit restricted (parental controls?)")
case .couldNotDetermine:
print("❌ Status unknown")
case .temporarilyUnavailable:
print("⚠️ CloudKit temporarily unavailable")
@unknown default:
print("⚠️ Unknown status")
}
}
}
Expected: .available or changes won't sync
Verify USB Meter.entitlements has:
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.ro.xdev.USB-Meter</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.ro.xdev.USB-Meter</string>
</array>
If missing: - ❌ App can't access CloudKit - ❌ No sync errors in logs (silent failure)
Enable enhanced Core Data debugging:
In Xcode scheme editor:
1. Select scheme → Edit Scheme → Run → Arguments
2. Add Launch Arguments:
-com.apple.CoreData.SQLDebug 1
-com.apple.CoreData.ConcurrencyDebug 1
In terminal: ```bash
log stream --predicate 'process == "USB Meter"' --level debug ```
Look for:
- NSPersistentCloudKitContainer initialization messages
- cloudkit: messages (sync operations)
- CoreData: messages (database operations)
// In AppDelegate.persistentContainer
if let description = container.persistentStoreDescriptions.first {
print("Store URL: \(description.url?.path ?? "nil")")
print("Options: \(description.options ?? [:])")
print("CloudKit container: \(description.cloudKitContainerOptions?.containerIdentifier ?? "nil")")
}
Expected:
- URL should point to .../Library/Application Support/CKModel.sqlite
- CloudKit container identifier must match entitlements
// In AppData
private func setupRemoteChangeNotificationObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(remoteStoreDidChange),
name: .NSPersistentStoreRemoteChange,
object: nil
)
print("✅ Remote change observer registered")
}
@objc private func remoteStoreDidChange() {
print("🔄 Remote CloudKit change received")
DispatchQueue.main.async {
self.reloadSettingsFromCloudStore()
}
}
Problem: If reloadSettingsFromCloudStore is never called, check if observer is registered
Diagnosis: ```swift // Check if save succeeded do { try appData.persistentContainer.viewContext.save() print("✅ Save successful") } catch { print("❌ Save failed: (error)") }
// Check if DeviceSettings record exists let request = NSFetchRequest(entityName: "DeviceSettings") let count = try? appData.persistentContainer.viewContext.count(for: request) print("Records in store: (count ?? 0)") ```
Fixes:
1. Check Account Status → must be .available
2. Check Merge Policy → ensure NSMergeByPropertyStoreTrumpMergePolicy
3. Check Observer → remoteStoreDidChange registered?
4. Force Sync:
swift
appData.persistentContainer.viewContext.refreshAllObjects()
appData.reloadSettingsFromCloudStore()
Diagnosis: ```swift // Check for MAC address duplicates let request = NSFetchRequest(entityName: "DeviceSettings") request.returnsDistinctResults = true request.returnsObjectsAsFaults = false request.resultType = .dictionaryResultType request.returnsDistinctResults = true
let macs = try? appData.persistentContainer.viewContext.fetch(request) .compactMap { $0["macAddress"] as? String }
let duplicates = macs?.filter { mac in macs?.filter { $0 == mac }.count ?? 0 > 1 } print("Duplicate MACs: (duplicates ?? [])") ```
Fixes:
1. Rebuild v3: Ensure cloudStoreRebuildVersion = 3 is deployed
2. Check rebuildCanonicalStoreIfNeeded:
swift
// Manually trigger
appData.cloudStore?.rebuildCanonicalStoreIfNeeded(version: 3)
3. Verify UserDefaults:
swift
let rebuilt = UserDefaults.standard.bool(forKey: "cloudStoreRebuildVersion.3")
print("Rebuild v3 completed: \(rebuilt)")
Diagnosis: ```swift // Check connection record let request = NSFetchRequest(entityName: "DeviceSettings") let records = try? appData.persistentContainer.viewContext.fetch(request)
for record in records ?? [] { if let expiry = record.value(forKey: "connectedExpiryAt") as? Date { let isExpired = expiry < Date.now print("✋ MAC (record.value(forKey: "macAddress") ?? "?") expires at (expiry), expired: (isExpired)") } } ```
Fixes:
1. Manually clear lock:
swift
appData.clearMeterConnection(macAddress: "aa:bb:cc:dd:ee:ff")
2. Check TTL hardcode: Should be 120s in setConnection()
3. Verify system clock on Device A (didn't fall asleep or drift)
NSError 1569Symptom:
NSError Domain=NSCocoaErrorDomain Code=1569
"The object's persistent store is not reachable"
Causes:
1. ❌ Old model with uniquenessConstraint conflicting with CloudKit
2. ❌ All attributes on DeviceSettings aren't optional="YES"
Fixes:
1. Verify model v2+: Check .xccurrentversion points to USB_Meter 2.xcdatamodel
2. Check attributes: All must be optional="YES" (inspect .xcdatamodel/contents)
3. Reset store if persists:
swift
// Delete local store to force rebuild from CloudKit
let storeURL = appData.persistentContainer.persistentStoreDescriptions.first?.url
let fileManager = FileManager.default
try? fileManager.removeItem(at: storeURL)
struct CloudKitDebugView: View {
@ObservedObject var appData: AppData
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("CloudKit Sync Status")
.font(.headline)
DebugItem(label: "Account", value: appData.cloudKitAccountStatus)
DebugItem(label: "Device ID", value: UIDevice.current.identifierForVendor?.uuidString ?? "?")
DebugItem(label: "Device Name", value: AppData.myDeviceName)
DebugItem(label: "Last Sync", value: formatDate(appData.lastSyncTime))
DebugItem(label: "Records", value: "\(appData.deviceSettingsCount)")
DebugItem(label: "Pending", value: "\(appData.pendingSyncCount)")
Button("Force Reload") {
appData.reloadSettingsFromCloudStore()
}
}
.padding()
.font(.caption)
}
}
struct DebugItem: View {
let label: String
let value: String
var body: some View {
HStack {
Text(label).font(.caption2).foregroundColor(.secondary)
Spacer()
Text(value).font(.caption).monospaced()
}
}
}
Using CloudKit Console (requires Apple developer account):
iCloud.ro.xdev.USB-MeterDeviceSettings records:
macAddressconnectedByDeviceID matches expected device UUIDsupdatedAt timestamps// Measure reloadSettingsFromCloudStore() performance
let start = Date()
appData.reloadSettingsFromCloudStore()
let duration = Date().timeIntervalSince(start)
print("Reload took \(duration * 1000)ms") // Should be <100ms for ~10 records
Benchmarks:
| Operation | Target | Actual (good) |
|-----------|--------|---------------|
| setConnection() | <50ms | ~10ms |
| reloadSettingsFromCloudStore() | <100ms | ~30ms (5 records) |
| CloudKit push (network) | <5s | varies by connection |
| Remote notification delivery | <10s | ~2-5s (typically) |
# Save full debug logs
log collect --start '2025-03-26 10:00:00' --output /tmp/cloudkit_logs.logarchive
# Extract readable format
log show /tmp/cloudkit_logs.logarchive \
--predicate 'process == "USB Meter"' \
--level debug > ~/cloudkit_debug.txt
Include in bug report:
1. ~/cloudkit_debug.txt
2. Reproduction steps
3. What you expected vs. what happened
4. Device names & OS versions involved