mcpField
A
axiom-cloud-sync-diag

axiom-cloud-sync-diag

github.com/charleswiltgen云与 DevOps1.2k180 次安装今天更新
代码仓库

关于 axiom-cloud-sync-diag

面向 Apple 平台开发者的 iCloud 同步诊断技能,提供检查账户、网络和权限配置的排查步骤,并覆盖 CloudKit 与 iCloud Drive 的常见错误。

主要功能

  1. 01账户与权限检查
  2. 02CloudKit 错误排查
  3. 03iCloud Drive 上传下载诊断
  4. 04同步冲突分析
  5. 05客户端日志与监控建议

适用场景

  1. 01排查设备间数据未同步
  2. 02诊断 CloudKit 配额或网络错误
  3. 03处理文件上传下载停滞
  4. 04调查更新后出现的同步故障

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-cloud-sync-diag

安装后在 Claude Code 中输入/axiom-cloud-sync-diag确认已加载。

详细介绍

iCloud 同步诊断

概述

核心原则:90% 的云同步问题源于账户/权限问题、网络连接,或对同步时机的误解,而非 iCloud 基础设施故障。

iCloud(包括 CloudKit 和 iCloud Drive)每天在所有 Apple 设备之间处理数十亿次同步操作。如果你的数据没有同步,问题几乎总是出在配置、连接或对时机的预期上。

警示信号——怀疑云同步问题

如果你遇到以下任一情况:

  • 文件/数据未显示在其他设备上
  • 出现“iCloud 账户不可用”错误
  • 持续出现同步冲突
  • CloudKit 配额已超出
  • 上传/下载卡在 0%
  • 在模拟器上正常,但在设备上不正常
  • 使用 WiFi 正常,但使用蜂窝网络不正常

❌ 禁止“iCloud 坏了,我们应该自建同步”

  • iCloud 基础设施处理数万亿次操作
  • 构建可靠的同步功能极其复杂
  • 99% 的问题都出在配置或连接上

必须首先执行的步骤

务必先检查以下内容(再更改代码):

// 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")
        }
    }
}

决策树

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)
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

常见 CloudKit 错误

CKError.accountTemporarilyUnavailable

原因:iCloud 服务器暂时不可用,或用户已退出登录

修复方法:

if error.code == .accountTemporarilyUnavailable {
    // Retry with exponential backoff
    try await Task.sleep(for: .seconds(5))
    try await retryOperation()
}
CKError.quotaExceeded

原因:用户的 iCloud 储存空间已满

修复方法:

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

原因:自上次获取记录以来,服务器上的记录已被修改。最常见的根本原因:未先获取最新版本,就保存了过期记录。

诊断——先检查这个简单的修复方法:

// ❌ WRONG: Saving without fetching latest version
// This causes serverRecordChanged on EVERY concurrent edit
let record = CKRecord(recordType: "Note", recordID: existingID)
record["title"] = "Updated"
try await database.save(record)  // Overwrites server version → conflict

// ✅ FIX: Fetch-then-modify-then-save (fixes 80% of cases)
let record = try await database.record(for: existingID)  // Get latest
record["title"] = "Updated"  // Modify the fetched record
try await database.save(record)  // Save with correct changeTag

如果先获取再保存仍无法解决(多个设备上的确存在并发编辑):

if error.code == .serverRecordChanged,
   let serverRecord = error.serverRecord,
   let clientRecord = error.clientRecord {
    // Merge records — only needed for real multi-device conflicts
    let merged = mergeRecords(server: serverRecord, client: clientRecord)
    try await database.save(merged)
}
CKError.networkUnavailable

原因:没有网络连接

修复方法:

if error.code == .networkUnavailable {
    // Queue for retry when online
    queueOperation(for: .whenOnline)

    // Or show offline indicator
    showOfflineIndicator()
}
批量操作中的数据静默丢失

症状:同步看似正常,但记录悄然消失或保存失败。

常见原因:

原因症状修复方法
记录大小 > 1 MB单条记录从批次中悄然丢弃将大型数据拆分到 CKAsset 中
批次部分失败部分记录保存成功,其他记录静默失败检查 perRecordSaveBlock 中每条记录的错误
冲突自动解决“最后写入者胜出”覆盖了有效数据实现基于合并的冲突解决机制
未触发资源下载记录已同步,但 CKAsset 内容缺失使用 desiredKeys 调用 fetchRecordZoneChanges

诊断:

// ❌ WRONG: Batch save with no per-record error handling
let operation = CKModifyRecordsOperation(recordsToSave: records)
operation.modifyRecordsResultBlock = { result in
    // Only catches operation-level failures — misses per-record errors
}

// ✅ CORRECT: Check each record individually
let operation = CKModifyRecordsOperation(recordsToSave: records)
operation.perRecordSaveBlock = { recordID, result in
    switch result {
    case .success(let record):
        print("✅ Saved: \(recordID)")
    case .failure(let error):
        print("❌ Failed: \(recordID) — \(error)")
        // Log for retry — this record was silently lost otherwise
    }
}

常见 iCloud Drive 错误

上传错误
// ✅ 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")
        }
    }
}
下载错误
// ✅ 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
    }
}

调试模式

模式 1:CloudKit 操作未完成

症状:保存/获取操作始终未完成,也没有错误

诊断:

// 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")
}

常见原因:

  • 没有网络连接
  • 账户问题
  • 操作过早取消
模式 2:SwiftData CloudKit 未同步

症状:SwiftData 在本地保存,但没有同步

诊断:

// 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
}
模式 3:文件协调器死锁

症状:文件操作挂起

诊断:

// ❌ 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)
}
模式 4:冲突未解决

症状:解决冲突后,冲突仍然存在

诊断:

// ❌ 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)

生产故障场景

症状:应用更新后,用户反馈数据未同步

诊断步骤(按顺序执行):

  1. 检查账户状态(2 分钟):

    // On affected device
    let status = FileManager.default.ubiquityIdentityToken
    // nil? → Not signed in
    
  2. 确认授权未变更(5 分钟):

    • 比较新旧构建版本的授权
    • 确认容器 ID 一致
  3. 检查是否存在不兼容变更(10 分钟):

    • CloudKit 架构是否有变更?
    • ubiquitous container ID 是否有变更?
    • 新旧版本是否兼容?
  4. 在干净设备上测试(15 分钟):

    • 将设备恢复出厂设置,或使用新的测试设备
    • 登录 iCloud
    • 安装应用
    • 全新安装后是否能正常同步?

根本原因(占 90% 的情况):

  • 构建中的授权发生变更或损坏
  • CloudKit 容器 ID 不匹配
  • 架构存在不兼容变更
  • 账户限制(新增家长控制等)

修复方法:

  • 验证构建中的授权
  • 测试从旧版本迁移的路径
  • 改进错误处理和用户提示

监控

CloudKit 控制台(推荐 — WWDC 2024)

访问地址:https://icloud.developer.apple.com/dashboard

监控内容:

  • 按类型统计的错误率
  • 延迟百分位数(p50、p95、p99)
  • 配额使用情况
  • 请求量

为以下情况设置警报:

  • 错误率过高(>5%)
  • 配额即将达到上限(>80%)
  • 延迟激增
客户端日志记录
// ✅ 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
        }
    }
}

快速诊断清单

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 ===")
}

相关技能

  • axiom-cloudkit-ref — CloudKit 实现详情
  • axiom-icloud-drive-ref — iCloud Drive 实现详情
  • axiom-storage — 选择同步方式

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

评论

支持 Markdown