axiom-cloud-sync-diag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseiCloud Sync Diagnostics
iCloud同步诊断
Overview
概述
Core principle 90% of cloud sync problems stem from account/entitlement issues, network connectivity, or misunderstanding sync timing—not iCloud infrastructure bugs.
iCloud (both CloudKit and iCloud Drive) handles billions of sync operations daily across all Apple devices. If your data isn't syncing, the issue is almost always configuration, connectivity, or timing expectations.
核心原则:90%的云同步问题源于账户/权限问题、网络连接问题,或者对同步时机的误解——而非iCloud基础设施漏洞。
iCloud(包括CloudKit和iCloud Drive)每天在所有苹果设备上处理数十亿次同步操作。如果你的数据无法同步,问题几乎总是出在配置、连接或对同步时机的预期上。
Red Flags — Suspect Cloud Sync Issue
危险信号——怀疑云同步问题
If you see ANY of these:
- Files/data not appearing on other devices
- "iCloud account not available" errors
- Persistent sync conflicts
- CloudKit quota exceeded
- Upload/download stuck at 0%
- Works on simulator but not device
- Works on WiFi but not cellular
❌ FORBIDDEN "iCloud is broken, we should build our own sync"
- iCloud infrastructure handles trillions of operations
- Building reliable sync is incredibly complex
- 99% of issues are configuration or connectivity
如果出现以下任何一种情况:
- 文件/数据未在其他设备上显示
- “iCloud账户不可用”错误
- 持续的同步冲突
- CloudKit配额超出
- 上传/下载卡在0%
- 在模拟器上可用但在真机上不可用
- 在WiFi下可用但在蜂窝网络下不可用
❌ 禁止:“iCloud坏了,我们应该自己构建同步功能”
- iCloud基础设施处理数万亿次操作
- 构建可靠的同步功能极其复杂
- 99%的问题都是配置或连接问题
Mandatory First Steps
必须首先执行的步骤
ALWAYS check these FIRST (before changing code):
swift
// 1. Check iCloud account status
func checkICloudStatus() async {
let status = FileManager.default.ubiquityIdentityToken
if status == nil {
print("❌ Not signed into iCloud")
print("Settings → [Name] → iCloud → Sign in")
return
}
print("✅ Signed into iCloud")
// For CloudKit specifically
let container = CKContainer.default()
do {
let status = try await container.accountStatus()
switch status {
case .available:
print("✅ CloudKit available")
case .noAccount:
print("❌ No iCloud account")
case .restricted:
print("❌ iCloud restricted (parental controls?)")
case .couldNotDetermine:
print("⚠️ Could not determine status")
case .temporarilyUnavailable:
print("⚠️ Temporarily unavailable (retry)")
@unknown default:
print("⚠️ Unknown status")
}
} catch {
print("Error checking CloudKit: \(error)")
}
}
// 2. Check entitlements
func checkEntitlements() {
// Verify iCloud container exists
if let containerURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) {
print("✅ iCloud container: \(containerURL)")
} else {
print("❌ No iCloud container")
print("Check Xcode → Signing & Capabilities → iCloud")
}
}
// 3. Check network connectivity
func checkConnectivity() {
// Use NWPathMonitor or similar
print("Network: Check if device has internet")
print("Try on different networks (WiFi, cellular)")
}
// 4. Check device storage
func checkStorage() {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
if let values = try? homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityKey
]) {
let available = values.volumeAvailableCapacity ?? 0
print("Available space: \(available / 1_000_000) MB")
if available < 100_000_000 { // <100 MB
print("⚠️ Low storage may prevent sync")
}
}
}务必先检查以下内容(在修改代码之前):
swift
// 1. Check iCloud account status
func checkICloudStatus() async {
let status = FileManager.default.ubiquityIdentityToken
if status == nil {
print("❌ Not signed into iCloud")
print("Settings → [Name] → iCloud → Sign in")
return
}
print("✅ Signed into iCloud")
// For CloudKit specifically
let container = CKContainer.default()
do {
let status = try await container.accountStatus()
switch status {
case .available:
print("✅ CloudKit available")
case .noAccount:
print("❌ No iCloud account")
case .restricted:
print("❌ iCloud restricted (parental controls?)")
case .couldNotDetermine:
print("⚠️ Could not determine status")
case .temporarilyUnavailable:
print("⚠️ Temporarily unavailable (retry)")
@unknown default:
print("⚠️ Unknown status")
}
} catch {
print("Error checking CloudKit: \(error)")
}
}
// 2. Check entitlements
func checkEntitlements() {
// Verify iCloud container exists
if let containerURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) {
print("✅ iCloud container: \(containerURL)")
} else {
print("❌ No iCloud container")
print("Check Xcode → Signing & Capabilities → iCloud")
}
}
// 3. Check network connectivity
func checkConnectivity() {
// Use NWPathMonitor or similar
print("Network: Check if device has internet")
print("Try on different networks (WiFi, cellular)")
}
// 4. Check device storage
func checkStorage() {
let homeURL = FileManager.default.homeDirectoryForCurrentUser
if let values = try? homeURL.resourceValues(forKeys: [
.volumeAvailableCapacityKey
]) {
let available = values.volumeAvailableCapacity ?? 0
print("Available space: \(available / 1_000_000) MB")
if available < 100_000_000 { // <100 MB
print("⚠️ Low storage may prevent sync")
}
}
}Decision Tree
决策树
CloudKit Sync Issues
CloudKit同步问题
CloudKit data not syncing?
├─ Account unavailable?
│ ├─ Check: await container.accountStatus()
│ ├─ .noAccount → User not signed into iCloud
│ ├─ .restricted → Parental controls or corporate restrictions
│ └─ .temporarilyUnavailable → Network issue or iCloud outage
│
├─ CKError.quotaExceeded?
│ └─ User exceeded iCloud storage quota
│ → Prompt user to purchase more storage
│ → Or delete old data
│
├─ CKError.networkUnavailable?
│ └─ No internet connection
│ → Check WiFi/cellular
│ → Test on different network
│
├─ CKError.serverRecordChanged (conflict)?
│ └─ Concurrent modifications
│ → Implement conflict resolution
│ → Use savePolicy correctly
│
└─ SwiftData not syncing?
├─ Check ModelConfiguration CloudKit setup
├─ Verify private database only (no public/shared)
└─ Check for @Attribute(.unique) (not supported with CloudKit)CloudKit数据未同步?
├─ 账户不可用?
│ ├─ 检查:await container.accountStatus()
│ ├─ .noAccount → 用户未登录iCloud
│ ├─ .restricted → 家长控制或企业限制
│ └─ .temporarilyUnavailable → 网络问题或iCloud服务中断
│
├─ CKError.quotaExceeded?
│ └─ 用户超出iCloud存储配额
│ → 提示用户购买更多存储
│ → 或删除旧数据
│
├─ CKError.networkUnavailable?
│ └─ 无网络连接
│ → 检查WiFi/蜂窝网络
│ → 在不同网络测试
│
├─ CKError.serverRecordChanged(冲突)?
│ └─ 并发修改
│ → 实现冲突解决逻辑
│ → 正确使用savePolicy
│
└─ SwiftData未同步?
├─ 检查ModelConfiguration的CloudKit设置
├─ 确认仅使用私有数据库(不使用公共/共享数据库)
└─ 检查是否存在@Attribute(.unique)(CloudKit不支持该属性)iCloud Drive Sync Issues
iCloud Drive同步问题
iCloud Drive files not syncing?
├─ File not uploading?
│ ├─ Check: url.resourceValues(.ubiquitousItemIsUploadingKey)
│ ├─ Check: url.resourceValues(.ubiquitousItemUploadingErrorKey)
│ └─ Error details will indicate issue
│
├─ File not downloading?
│ ├─ Not requested? → startDownloadingUbiquitousItem(at:)
│ ├─ Check: url.resourceValues(.ubiquitousItemDownloadingErrorKey)
│ └─ May need manual download trigger
│
├─ File has conflicts?
│ ├─ Check: url.resourceValues(.ubiquitousItemHasUnresolvedConflictsKey)
│ └─ Resolve with NSFileVersion
│
└─ Files not appearing on other device?
├─ Check iCloud account on both devices (same account?)
├─ Check entitlements match on both
├─ Wait (sync not instant, can take minutes)
└─ Check Settings → iCloud → iCloud Drive → [App] is enablediCloud Drive文件未同步?
├─ 文件未上传?
│ ├─ 检查:url.resourceValues(.ubiquitousItemIsUploadingKey)
│ ├─ 检查:url.resourceValues(.ubiquitousItemUploadingErrorKey)
│ └─ 错误详情会指明问题
│
├─ 文件未下载?
│ ├─ 未发起请求?→ 调用startDownloadingUbiquitousItem(at:)
│ ├─ 检查:url.resourceValues(.ubiquitousItemDownloadingErrorKey)
│ └─ 可能需要手动触发下载
│
├─ 文件存在冲突?
│ ├─ 检查:url.resourceValues(.ubiquitousItemHasUnresolvedConflictsKey)
│ └─ 使用NSFileVersion解决冲突
│
└─ 文件未在其他设备显示?
├─ 检查两台设备的iCloud账户(是否为同一账户?)
├─ 检查两台设备的权限是否匹配
├─ 等待(同步并非即时,可能需要数分钟)
└─ 检查设置 → iCloud → iCloud Drive → [应用]是否已启用Common CloudKit Errors
常见CloudKit错误
CKError.accountTemporarilyUnavailable
CKError.accountTemporarilyUnavailable
Cause: iCloud servers temporarily unavailable or user signed out
Fix:
swift
if error.code == .accountTemporarilyUnavailable {
// Retry with exponential backoff
try await Task.sleep(for: .seconds(5))
try await retryOperation()
}原因:iCloud服务器暂时不可用或用户已退出登录
修复方案:
swift
if error.code == .accountTemporarilyUnavailable {
// Retry with exponential backoff
try await Task.sleep(for: .seconds(5))
try await retryOperation()
}CKError.quotaExceeded
CKError.quotaExceeded
Cause: User's iCloud storage full
Fix:
swift
if error.code == .quotaExceeded {
// Show alert to user
showAlert(
title: "iCloud Storage Full",
message: "Please free up space in Settings → [Name] → iCloud → Manage Storage"
)
}原因:用户的iCloud存储已满
修复方案:
swift
if error.code == .quotaExceeded {
// Show alert to user
showAlert(
title: "iCloud Storage Full",
message: "Please free up space in Settings → [Name] → iCloud → Manage Storage"
)
}CKError.serverRecordChanged
CKError.serverRecordChanged
Cause: Conflict - record modified on server since fetch
Fix:
swift
if error.code == .serverRecordChanged,
let serverRecord = error.serverRecord,
let clientRecord = error.clientRecord {
// Merge records
let merged = mergeRecords(server: serverRecord, client: clientRecord)
// Retry with merged version
try await database.save(merged)
}原因:冲突——记录在获取后已在服务器上被修改
修复方案:
swift
if error.code == .serverRecordChanged,
let serverRecord = error.serverRecord,
let clientRecord = error.clientRecord {
// Merge records
let merged = mergeRecords(server: serverRecord, client: clientRecord)
// Retry with merged version
try await database.save(merged)
}CKError.networkUnavailable
CKError.networkUnavailable
Cause: No internet connection
Fix:
swift
if error.code == .networkUnavailable {
// Queue for retry when online
queueOperation(for: .whenOnline)
// Or show offline indicator
showOfflineIndicator()
}原因:无网络连接
修复方案:
swift
if error.code == .networkUnavailable {
// Queue for retry when online
queueOperation(for: .whenOnline)
// Or show offline indicator
showOfflineIndicator()
}Common iCloud Drive Errors
常见iCloud Drive错误
Upload Errors
上传错误
swift
// ✅ Check upload error
func checkUploadError(url: URL) {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemUploadingErrorKey
])
if let error = values?.ubiquitousItemUploadingError {
print("Upload error: \(error.localizedDescription)")
if (error as NSError).code == NSFileWriteOutOfSpaceError {
print("iCloud storage full")
}
}
}swift
// ✅ Check upload error
func checkUploadError(url: URL) {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemUploadingErrorKey
])
if let error = values?.ubiquitousItemUploadingError {
print("Upload error: \(error.localizedDescription)")
if (error as NSError).code == NSFileWriteOutOfSpaceError {
print("iCloud storage full")
}
}
}Download Errors
下载错误
swift
// ✅ Check download error
func checkDownloadError(url: URL) {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingErrorKey
])
if let error = values?.ubiquitousItemDownloadingError {
print("Download error: \(error.localizedDescription)")
// Common errors:
// - Network unavailable
// - Account unavailable
// - File deleted on server
}
}swift
// ✅ Check download error
func checkDownloadError(url: URL) {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingErrorKey
])
if let error = values?.ubiquitousItemDownloadingError {
print("Download error: \(error.localizedDescription)")
// Common errors:
// - Network unavailable
// - Account unavailable
// - File deleted on server
}
}Debugging Patterns
调试模式
Pattern 1: CloudKit Operation Not Completing
模式1:CloudKit操作未完成
Symptom: Save/fetch never completes, no error
Diagnosis:
swift
// Add timeout
Task {
try await withTimeout(seconds: 30) {
try await database.save(record)
}
}
// Log operation lifecycle
operation.database = database
operation.completionBlock = {
print("Operation completed")
}
operation.qualityOfService = .userInitiated
// Check if operation was cancelled
if operation.isCancelled {
print("Operation was cancelled")
}Common causes:
- No network connectivity
- Account issues
- Operation cancelled prematurely
症状:保存/获取操作从未完成,无错误提示
诊断:
swift
// Add timeout
Task {
try await withTimeout(seconds: 30) {
try await database.save(record)
}
}
// Log operation lifecycle
operation.database = database
operation.completionBlock = {
print("Operation completed")
}
operation.qualityOfService = .userInitiated
// Check if operation was cancelled
if operation.isCancelled {
print("Operation was cancelled")
}常见原因:
- 无网络连接
- 账户问题
- 操作被提前取消
Pattern 2: SwiftData CloudKit Not Syncing
模式2:SwiftData CloudKit未同步
Symptom: SwiftData saves locally but doesn't sync
Diagnosis:
swift
// 1. Verify CloudKit configuration
let config = ModelConfiguration(
cloudKitDatabase: .private("iCloud.com.example.app")
)
// 2. Check for incompatible attributes
// ❌ @Attribute(.unique) not supported with CloudKit
@Model
class Task {
@Attribute(.unique) var id: UUID // ← Remove this
var title: String
}
// 3. Check all properties have defaults or are optional
@Model
class Task {
var title: String = "" // ✅ Has default
var dueDate: Date? // ✅ Optional
}症状:SwiftData在本地保存但未同步
诊断:
swift
// 1. Verify CloudKit configuration
let config = ModelConfiguration(
cloudKitDatabase: .private("iCloud.com.example.app")
)
// 2. Check for incompatible attributes
// ❌ @Attribute(.unique) not supported with CloudKit
@Model
class Task {
@Attribute(.unique) var id: UUID // ← Remove this
var title: String
}
// 3. Check all properties have defaults or are optional
@Model
class Task {
var title: String = "" // ✅ Has default
var dueDate: Date? // ✅ Optional
}Pattern 3: File Coordinator Deadlock
模式3:文件协调器死锁
Symptom: File operations hang
Diagnosis:
swift
// ❌ WRONG: Nested coordination can deadlock
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
// Don't create another coordinator here!
anotherCoordinator.coordinate(...) // ← Deadlock risk
}
// ✅ CORRECT: Single coordinator per operation
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
// Direct file operations only
try data.write(to: newURL)
}症状:文件操作挂起
诊断:
swift
// ❌ WRONG: Nested coordination can deadlock
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
// Don't create another coordinator here!
anotherCoordinator.coordinate(...) // ← Deadlock risk
}
// ✅ CORRECT: Single coordinator per operation
coordinator.coordinate(writingItemAt: url, options: [], error: nil) { newURL in
// Direct file operations only
try data.write(to: newURL)
}Pattern 4: Conflicts Not Resolving
模式4:冲突未解决
Symptom: Conflicts persist even after resolution
Diagnosis:
swift
// ❌ WRONG: Not marking as resolved
let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
for conflict in conflicts ?? [] {
// Missing: conflict.isResolved = true
}
// ✅ CORRECT: Mark resolved and remove
for conflict in conflicts ?? [] {
conflict.isResolved = true
}
try NSFileVersion.removeOtherVersionsOfItem(at: url)症状:即使进行了冲突处理,冲突仍然存在
诊断:
swift
// ❌ WRONG: Not marking as resolved
let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
for conflict in conflicts ?? [] {
// Missing: conflict.isResolved = true
}
// ✅ CORRECT: Mark resolved and remove
for conflict in conflicts ?? [] {
conflict.isResolved = true
}
try NSFileVersion.removeOtherVersionsOfItem(at: url)Production Crisis Scenario
生产环境紧急场景
SYMPTOM: Users report data not syncing after app update
DIAGNOSIS STEPS (run in order):
-
Check account status (2 min):swift
// On affected device let status = FileManager.default.ubiquityIdentityToken // nil? → Not signed in -
Verify entitlements unchanged (5 min):
- Compare old vs new build entitlements
- Verify container IDs match
-
Check for breaking changes (10 min):
- Did CloudKit schema change?
- Did ubiquitous container ID change?
- Are old and new versions compatible?
-
Test on clean device (15 min):
- Factory reset device or use new test device
- Sign into iCloud
- Install app
- Does sync work on fresh install?
ROOT CAUSES (90% of cases):
- Entitlements changed/corrupted in build
- CloudKit container ID mismatch
- Breaking schema changes
- Account restrictions (new parental controls, etc.)
FIX:
- Verify entitlements in build
- Test migration path from old version
- Add better error handling and user messaging
症状:用户报告应用更新后数据无法同步
诊断步骤(按顺序执行):
-
检查账户状态(2分钟):swift
// On affected device let status = FileManager.default.ubiquityIdentityToken // nil? → Not signed in -
验证权限未更改(5分钟):
- 对比新旧版本的构建权限
- 验证容器ID匹配
-
检查是否存在破坏性变更(10分钟):
- CloudKit架构是否变更?
- 通用容器ID是否变更?
- 新旧版本是否兼容?
-
在干净设备上测试(15分钟):
- 重置设备或使用新的测试设备
- 登录iCloud
- 安装应用
- 全新安装后同步是否正常?
根本原因(90%的情况):
- 构建中的权限被更改/损坏
- CloudKit容器ID不匹配
- 破坏性架构变更
- 账户限制(新的家长控制等)
修复方案:
- 验证构建中的权限
- 测试从旧版本到新版本的迁移路径
- 添加更好的错误处理和用户提示
Monitoring
监控
CloudKit Console (recommended - WWDC 2024)
CloudKit控制台(推荐 - WWDC 2024)
Monitor:
- Error rates by type
- Latency percentiles (p50, p95, p99)
- Quota usage
- Request volume
Set alerts for:
- High error rate (>5%)
- Quota approaching limit (>80%)
- Latency spikes
监控内容:
- 按类型统计的错误率
- 延迟百分位数(p50、p95、p99)
- 配额使用情况
- 请求量
设置告警的场景:
- 高错误率(>5%)
- 配额接近上限(>80%)
- 延迟峰值
Client-Side Logging
客户端日志
swift
// ✅ Log all CloudKit operations
extension CKDatabase {
func saveWithLogging(_ record: CKRecord) async throws {
print("Saving record: \(record.recordID)")
let start = Date()
do {
try await self.save(record)
let duration = Date().timeIntervalSince(start)
print("✅ Saved in \(duration)s")
} catch let error as CKError {
print("❌ Save failed: \(error.code), \(error.localizedDescription)")
throw error
}
}
}swift
// ✅ Log all CloudKit operations
extension CKDatabase {
func saveWithLogging(_ record: CKRecord) async throws {
print("Saving record: \(record.recordID)")
let start = Date()
do {
try await self.save(record)
let duration = Date().timeIntervalSince(start)
print("✅ Saved in \(duration)s")
} catch let error as CKError {
print("❌ Save failed: \(error.code), \(error.localizedDescription)")
throw error
}
}
}Quick Diagnostic Checklist
快速诊断检查表
swift
func diagnoseCloudSyncIssue() async {
print("=== Cloud Sync Diagnosis ===")
// 1. Account
await checkICloudStatus()
// 2. Entitlements
checkEntitlements()
// 3. Network
checkConnectivity()
// 4. Storage
checkStorage()
// 5. For CloudKit
let container = CKContainer.default()
do {
let status = try await container.accountStatus()
print("CloudKit status: \(status)")
} catch {
print("CloudKit error: \(error)")
}
// 6. For iCloud Drive
if let url = getICloudContainerURL() {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingErrorKey,
.ubiquitousItemUploadingErrorKey
])
print("Download error: \(values?.ubiquitousItemDownloadingError?.localizedDescription ?? "none")")
print("Upload error: \(values?.ubiquitousItemUploadingError?.localizedDescription ?? "none")")
}
print("=== End Diagnosis ===")
}swift
func diagnoseCloudSyncIssue() async {
print("=== Cloud Sync Diagnosis ===")
// 1. Account
await checkICloudStatus()
// 2. Entitlements
checkEntitlements()
// 3. Network
checkConnectivity()
// 4. Storage
checkStorage()
// 5. For CloudKit
let container = CKContainer.default()
do {
let status = try await container.accountStatus()
print("CloudKit status: \(status)")
} catch {
print("CloudKit error: \(error)")
}
// 6. For iCloud Drive
if let url = getICloudContainerURL() {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingErrorKey,
.ubiquitousItemUploadingErrorKey
])
print("Download error: \(values?.ubiquitousItemDownloadingError?.localizedDescription ?? "none")")
print("Upload error: \(values?.ubiquitousItemUploadingError?.localizedDescription ?? "none")")
}
print("=== End Diagnosis ===")
}Related Skills
相关技能
- — CloudKit implementation details
axiom-cloudkit-ref - — iCloud Drive implementation details
axiom-icloud-drive-ref - — Choose sync approach
axiom-storage
Last Updated: 2025-12-12
Skill Type: Diagnostic
- — CloudKit实现细节
axiom-cloudkit-ref - — iCloud Drive实现细节
axiom-icloud-drive-ref - — 选择同步方案
axiom-storage
最后更新:2025-12-12
技能类型:诊断