关于 axiom-cloud-sync-diag
面向 Apple 平台开发者的 iCloud 同步诊断技能,提供检查账户、网络和权限配置的排查步骤,并覆盖 CloudKit 与 iCloud Drive 的常见错误。
主要功能
- 01账户与权限检查
- 02CloudKit 错误排查
- 03iCloud Drive 上传下载诊断
- 04同步冲突分析
- 05客户端日志与监控建议
适用场景
- 01排查设备间数据未同步
- 02诊断 CloudKit 配额或网络错误
- 03处理文件上传下载停滞
- 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)
生产故障场景
症状:应用更新后,用户反馈数据未同步
诊断步骤(按顺序执行):
-
检查账户状态(2 分钟):
// On affected device let status = FileManager.default.ubiquityIdentityToken // nil? → Not signed in -
确认授权未变更(5 分钟):
- 比较新旧构建版本的授权
- 确认容器 ID 一致
-
检查是否存在不兼容变更(10 分钟):
- CloudKit 架构是否有变更?
- ubiquitous container ID 是否有变更?
- 新旧版本是否兼容?
-
在干净设备上测试(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 技能类型:诊断