Newer Older
177 lines | 6.179kb
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.