axiom-cloud-sync-diag

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iCloud 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 enabled

iCloud 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):
  1. Check account status (2 min):
    swift
    // On affected device
    let status = FileManager.default.ubiquityIdentityToken
    // nil? → Not signed in
  2. Verify entitlements unchanged (5 min):
    • Compare old vs new build entitlements
    • Verify container IDs match
  3. Check for breaking changes (10 min):
    • Did CloudKit schema change?
    • Did ubiquitous container ID change?
    • Are old and new versions compatible?
  4. 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

症状:用户报告应用更新后数据无法同步
诊断步骤(按顺序执行):
  1. 检查账户状态(2分钟):
    swift
    // On affected device
    let status = FileManager.default.ubiquityIdentityToken
    // nil? → Not signed in
  2. 验证权限未更改(5分钟):
    • 对比新旧版本的构建权限
    • 验证容器ID匹配
  3. 检查是否存在破坏性变更(10分钟):
    • CloudKit架构是否变更?
    • 通用容器ID是否变更?
    • 新旧版本是否兼容?
  4. 在干净设备上测试(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

相关技能

  • axiom-cloudkit-ref
    — CloudKit implementation details
  • axiom-icloud-drive-ref
    — iCloud Drive implementation details
  • axiom-storage
    — Choose sync approach

Last Updated: 2025-12-12 Skill Type: Diagnostic
  • axiom-cloudkit-ref
    — CloudKit实现细节
  • axiom-icloud-drive-ref
    — iCloud Drive实现细节
  • axiom-storage
    — 选择同步方案

最后更新:2025-12-12 技能类型:诊断