axiom-cloud-sync-diag について
iCloud 同期を調査する Apple プラットフォーム開発者向けの診断スキルです。アカウント、接続、権限の確認手順と、CloudKit および iCloud Drive の一般的なエラーを扱います。
主な機能
- 01アカウントと権限の確認
- 02CloudKit エラーの診断
- 03iCloud Drive 転送の診断
- 04同期競合の分析
- 05クライアントログと監視の指針
活用シーン
- 01デバイス間でデータが届かない原因の調査
- 02CloudKit の容量やネットワークエラーの診断
- 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 スキーマに変更がありましたか?
- ユビキタスコンテナ ID に変更がありましたか?
- 旧バージョンと新バージョンに互換性がありますか?
-
クリーンなデバイスでテスト(15 分):
- デバイスを工場出荷時の状態にリセットするか、新しいテストデバイスを使用します
- iCloud にサインインします
- アプリをインストールします
- 新規インストール時に同期は機能しますか?
根本原因(ケースの 90%):
- ビルド内のエンタイトルメントが変更または破損している
- CloudKit コンテナ ID が一致していない
- スキーマに互換性を損なう変更がある
- アカウントの制限(新しいペアレンタルコントロールなど)
修正方法:
- ビルド内のエンタイトルメントを確認します
- 旧バージョンからの移行経路をテストします
- エラー処理とユーザー向けメッセージを改善します
モニタリング
CloudKit Console(推奨 - 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 スキルの種類:診断