|
Bogdan Timofte
authored
2 weeks ago
|
1
|
# CloudKit Sync Documentation
|
|
|
2
|
|
|
|
3
|
Complete reference for USB Meter's multi-device synchronization system using CloudKit.
|
|
|
4
|
|
|
|
5
|
---
|
|
|
6
|
|
|
|
7
|
## Quick Start
|
|
|
8
|
|
|
|
9
|
**Problem:** How do settings sync across my iPhone, iPad, and Mac?
|
|
|
10
|
|
|
|
11
|
**Answer:**
|
|
|
12
|
1. When you connect a meter on iPhone → `Meter.swift` calls `appData.publishMeterConnection()`
|
|
|
13
|
2. This updates a **DeviceSettings** record in Core Data
|
|
|
14
|
3. `NSPersistentCloudKitContainer` automatically syncs it to iCloud
|
|
|
15
|
4. Your iPad + Mac receive a notification → refresh UI
|
|
|
16
|
5. Within 2-5 seconds, all devices show "Connected on iPhone"
|
|
|
17
|
|
|
|
18
|
---
|
|
|
19
|
|
|
|
20
|
## Documentation Files
|
|
|
21
|
|
|
|
22
|
### 1. **SCHEMA.md** — Data Model & Structure
|
|
|
23
|
- DeviceSettings entity attributes
|
|
|
24
|
- CloudKit record mapping
|
|
|
25
|
- Why `uniquenessConstraint` was removed
|
|
|
26
|
- Version history (v1 → v2 → v3)
|
|
|
27
|
|
|
|
28
|
**Read this if:** You need to understand the data structure, add fields, or debug data corruption.
|
|
|
29
|
|
|
|
30
|
### 2. **MECHANISM.md** — How Sync Works
|
|
|
31
|
- Complete architecture diagram
|
|
|
32
|
- Step-by-step local write flow
|
|
|
33
|
- Remote change reception flow
|
|
|
34
|
- Core AppData methods explained
|
|
|
35
|
- Connection lifecycle (TTL, expiry, locking)
|
|
|
36
|
- Error handling & recovery
|
|
|
37
|
|
|
|
38
|
**Read this if:** You're troubleshooting sync issues, understanding threading, or extending functionality.
|
|
|
39
|
|
|
|
40
|
### 3. **TROUBLESHOOTING.md** — Debugging Guide
|
|
|
41
|
- Configuration verification checklist
|
|
|
42
|
- Common issues & fixes
|
|
|
43
|
- Real-time monitoring code
|
|
|
44
|
- Performance benchmarks
|
|
|
45
|
- Log analysis
|
|
|
46
|
|
|
|
47
|
**Read this if:** Sync isn't working, you want to add debug UI, or need production troubleshooting.
|
|
|
48
|
|
|
|
49
|
---
|
|
|
50
|
|
|
|
51
|
## Key Concepts
|
|
|
52
|
|
|
|
53
|
### DeviceSettings Record
|
|
|
54
|
Represents a single Bluetooth meter device with its metadata:
|
|
|
55
|
|
|
|
56
|
```
|
|
|
57
|
macAddress: "aa:bb:cc:dd:ee:ff" ← identifies the meter
|
|
|
58
|
meterName: "Kitchen Meter" ← user-friendly name
|
|
|
59
|
modelType: "TC66C" ← hardware type
|
|
|
60
|
connectedByDeviceID: "UUID" ← which device is using it now
|
|
|
61
|
connectedExpiryAt: Date ← connection lock TTL (120s)
|
|
|
62
|
updatedAt: Date ← last sync timestamp
|
|
|
63
|
```
|
|
|
64
|
|
|
|
65
|
### Sync Heartbeat
|
|
|
66
|
- **Connect:** `publishMeterConnection()` → saves record → syncs
|
|
|
67
|
- **Disconnect:** `clearMeterConnection()` → clears record → syncs
|
|
|
68
|
- **Refresh:** Periodically calls `reloadSettingsFromCloudStore()` to check for remote changes
|
|
|
69
|
|
|
|
70
|
### Conflict Resolution
|
|
|
71
|
If Device A and B edit the same record simultaneously:
|
|
|
72
|
- **Policy:** `NSMergeByPropertyStoreTrumpMergePolicy`
|
|
|
73
|
- **Rule:** CloudKit's version always wins
|
|
|
74
|
- **Edge case:** User might see their local change revert (~1s after)
|
|
|
75
|
|
|
|
76
|
---
|
|
|
77
|
|
|
|
78
|
## Architecture Summary
|
|
|
79
|
|
|
|
80
|
```
|
|
|
81
|
Meter UI ──→ BT Connection ──→ AppData ──→ CloudDeviceSettingsStore ──→ Core Data
|
|
|
82
|
↓ ↓
|
|
|
83
|
[CloudKit Sync] [persisted locally]
|
|
|
84
|
↓
|
|
|
85
|
[iCloud Servers]
|
|
|
86
|
↓
|
|
|
87
|
Other Devices ──→ UI Updates
|
|
|
88
|
```
|
|
|
89
|
|
|
|
90
|
---
|
|
|
91
|
|
|
|
92
|
## Critical Files in Codebase
|
|
|
93
|
|
|
|
94
|
| File | Role |
|
|
|
95
|
|------|------|
|
|
|
96
|
| `AppDelegate.swift` | Creates `NSPersistentCloudKitContainer` (line 85-127) |
|
|
|
97
|
| `SceneDelegate.swift` | Calls `activateCloudDeviceSync()` at startup |
|
|
|
98
|
| `AppData.swift` | Orchestrates sync: `publishMeterConnection`, `clearMeterConnection`, `reloadSettingsFromCloudStore` |
|
|
|
99
|
| `Meter.swift` | Line 109, 118, 132: Calls `appData` methods on state changes |
|
|
|
100
|
| `CKModel.xcdatamodeld/USB_Meter 2.xcdatamodel/contents` | Core Data schema (no uniqueness constraint) |
|
|
|
101
|
| `USB Meter.entitlements` | iCloud container + CloudKit permissions |
|
|
|
102
|
|
|
|
103
|
---
|
|
|
104
|
|
|
|
105
|
## Debugging Checklist
|
|
|
106
|
|
|
|
107
|
- [ ] CloudKit account status = `.available`?
|
|
|
108
|
- [ ] Entitlements file includes `iCloud.ro.xdev.USB-Meter`?
|
|
|
109
|
- [ ] Model version is `USB_Meter 2.xcdatamodel` or later?
|
|
|
110
|
- [ ] `cloudStoreRebuildVersion` ≥ 3?
|
|
|
111
|
- [ ] Remote change observer registered in `setupRemoteChangeNotificationObserver()`?
|
|
|
112
|
- [ ] Merge policy = `NSMergeByPropertyStoreTrumpMergePolicy`?
|
|
|
113
|
|
|
|
114
|
---
|
|
|
115
|
|
|
|
116
|
## Common Workflows
|
|
|
117
|
|
|
|
118
|
### "Settings won't sync to my other device"
|
|
|
119
|
1. Check CloudKit account (TROUBLESHOOTING.md § 1)
|
|
|
120
|
2. Verify entitlements (TROUBLESHOOTING.md § 2)
|
|
|
121
|
3. Force reload: `appData.reloadSettingsFromCloudStore()`
|
|
|
122
|
4. Check logs for `NSPersistentStoreRemoteChange` notifications
|
|
|
123
|
|
|
|
124
|
### "I see duplicate meter entries"
|
|
|
125
|
1. Verify `cloudStoreRebuildVersion = 3` deployed
|
|
|
126
|
2. Check `rebuildCanonicalStoreIfNeeded` ran (UserDefaults `cloudStoreRebuildVersion.3`)
|
|
|
127
|
3. If duplicates persist, see TROUBLESHOOTING.md § Issue 2
|
|
|
128
|
|
|
|
129
|
### "Connection lock doesn't expire"
|
|
|
130
|
1. Check system clock on Device A (ParentalControls?)
|
|
|
131
|
2. Manual unlock: `appData.clearMeterConnection(mac: "...")`
|
|
|
132
|
3. Verify TTL is 120s in `setConnection()`
|
|
|
133
|
|
|
|
134
|
### "I need to add a new sync field"
|
|
|
135
|
1. Edit `USB_Meter 2.xcdatamodel/contents` → add attribute (optional!)
|
|
|
136
|
2. Bump Core Data version if needed (lightweight migration)
|
|
|
137
|
3. Update `CloudDeviceSettingsStore` to populate it
|
|
|
138
|
4. Sync resumes automatically
|
|
|
139
|
|
|
|
140
|
---
|
|
|
141
|
|
|
|
142
|
## Performance
|
|
|
143
|
|
|
|
144
|
- **Local write latency:** ~10ms
|
|
|
145
|
- **CloudKit network push:** ~1-2s (typical)
|
|
|
146
|
- **Remote notification delivery:** ~2-5s
|
|
|
147
|
- **UI update after notification:** <100ms
|
|
|
148
|
- **Full reload (10 records):** ~50ms
|
|
|
149
|
|
|
|
150
|
Total end-to-end for a setting change: **~5-8 seconds** (typical, good network)
|
|
|
151
|
|
|
|
152
|
---
|
|
|
153
|
|
|
|
154
|
## Testing Tips
|
|
|
155
|
|
|
|
156
|
Use two devices on same iCloud account:
|
|
|
157
|
|
|
|
158
|
1. **Connect meter on iPhone** → Check iPad within 5s for "Connected on iPhone"
|
|
|
159
|
2. **Rename meter on iPhone** → Name updates on iPad instantly (local) after sync
|
|
|
160
|
3. **Airplane mode iPhone** → Changes queue locally
|
|
|
161
|
4. **Reconnect iPhone** → Changes resync automatically
|
|
|
162
|
5. **Switch iCloud accounts** → No data loss (encrypted in CloudKit)
|
|
|
163
|
|
|
|
164
|
---
|
|
|
165
|
|
|
|
166
|
## References
|
|
|
167
|
|
|
|
168
|
- [NSPersistentCloudKitContainer docs](https://developer.apple.com/documentation/coredata/nspersistentcloudkitcontainer)
|
|
|
169
|
- [CloudKit best practices](https://developer.apple.com/documentation/cloudkit/best_practices)
|
|
|
170
|
- [Core Data concurrency](https://developer.apple.com/documentation/coredata/concurrency)
|
|
|
171
|
- [Merging edited Core Data objects](https://developer.apple.com/documentation/coredata/nsmergepolicy)
|
|
|
172
|
|
|
|
173
|
---
|
|
|
174
|
|
|
|
175
|
## Questions?
|
|
|
176
|
|
|
|
177
|
Refer to specific sections above by file, or check the troubleshooting checklist in TROUBLESHOOTING.md.
|