axiom-icloud-drive-ref について
iOS開発者向けのiCloud Driveファイル同期リファレンススキルです。ファイル調整、ダウンロード状況、競合処理を扱い、CloudKitやキー値ストアとの使い分けも説明します。
主な機能
- 01iCloudコンテナ内のファイル管理
- 02NSFileCoordinatorによる読み書きの調整
- 03ファイル状態の確認とダウンロード要求
- 04ファイルバージョン競合の検出と解決
- 05小規模な設定値の同期
活用シーン
- 01ドキュメントアプリのデバイス間同期
- 02iCloudファイルの安全な読み書き
- 03同期競合とダウンロード状況の処理
- 04少量のユーザー設定の同期
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-icloud-drive-refインストール後、Claude Code で/axiom-icloud-drive-refを入力して読み込まれたか確認します。
詳細
iCloud Drive リファレンス
目的: ユビキタスコンテナを使用したファイルベースの iCloud 同期に関する包括的なリファレンス 利用可能バージョン: iOS 5.0+(基本機能)、iOS 8.0+(iCloud Drive)、iOS 11.0+(最新 API) コンテキスト: ファイルベースのクラウドストレージであり、データベースではありません(構造化データには CloudKit を使用してください)
このスキルを使用する場面
次の場合にこのスキルを使用してください:
- ドキュメントベースの iCloud 同期を実装する
- デバイス間でユーザーファイルを同期する
- ドキュメントベースのアプリ(Pages、Numbers など)を構築する
- プロセス間のファイルアクセスを調整する
- iCloud ファイルの競合に対処する
- 環境設定に NSUbiquitousKeyValueStore を使用する
対象外:リレーションシップを持つ構造化データ(代わりに axiom-cloudkit-ref を使用してください)
概要
iCloud Drive はファイルベースの同期用であり、構造化データ用ではありません。
次の場合に使用します:
- ユーザーがドキュメントを作成・編集する
- Dropbox のようにファイルを同期する必要がある
- ドキュメントピッカーとの統合
次の場合は使用しないでください:
- クエリ可能な構造化データが必要(CloudKit を使用してください)
- レコード間のリレーションシップが必要(CloudKit を使用してください)
- 小規模なキーと値の環境設定(NSUbiquitousKeyValueStore を使用してください)
ユビキタスコンテナ
ユビキタスコンテナの URL を取得する
// ✅ CORRECT: Get iCloud container
func getICloudContainerURL() -> URL? {
// nil = use first container in entitlements
return FileManager.default.url(
forUbiquityContainerIdentifier: nil
)
}
// ✅ Check if iCloud is available
if let iCloudURL = getICloudContainerURL() {
print("iCloud available: \(iCloudURL)")
} else {
print("iCloud not available (not signed in or no entitlement)")
}
コンテナ構造
iCloud Container/
├── Documents/ # User-visible files (Files app)
│ └── MyApp/ # Your app's documents
├── Library/ # Hidden from user
│ ├── Application Support/
│ └── Caches/
iCloud Drive に保存する
// ✅ CORRECT: Save document to iCloud
func saveToICloud(data: Data, filename: String) throws {
guard let iCloudURL = FileManager.default.url(
forUbiquityContainerIdentifier: nil
) else {
throw iCloudError.notAvailable
}
let documentsURL = iCloudURL.appendingPathComponent("Documents")
// Create directory if needed
try FileManager.default.createDirectory(
at: documentsURL,
withIntermediateDirectories: true
)
let fileURL = documentsURL.appendingPathComponent(filename)
// Use file coordination for safe access
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(
writingItemAt: fileURL,
options: .forReplacing,
error: &error
) { newURL in
try? data.write(to: newURL)
}
if let error = error {
throw error
}
}
ファイル調整(安全性に不可欠)
iCloud ファイルにアクセスするときは、必ず NSFileCoordinator を使用してください。これにより、次の問題を防止できます:
- 同期に伴う競合状態
- データの破損
- 更新の消失
ファイルの読み取り
// ✅ CORRECT: Coordinated read
func readICloudFile(url: URL) throws -> Data {
let coordinator = NSFileCoordinator()
var data: Data?
var coordinationError: NSError?
coordinator.coordinate(
readingItemAt: url,
options: [],
error: &coordinationError
) { newURL in
data = try? Data(contentsOf: newURL)
}
if let error = coordinationError {
throw error
}
guard let data = data else {
throw fileError.readFailed
}
return data
}
ファイルの書き込み
// ✅ CORRECT: Coordinated write
func writeICloudFile(data: Data, to url: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: url,
options: .forReplacing,
error: &coordinationError
) { newURL in
try? data.write(to: newURL)
}
if let error = coordinationError {
throw error
}
}
ファイルの移動
// ✅ CORRECT: Coordinated move
func moveFile(from sourceURL: URL, to destURL: URL) throws {
let coordinator = NSFileCoordinator()
var coordinationError: NSError?
coordinator.coordinate(
writingItemAt: sourceURL,
options: .forMoving,
writingItemAt: destURL,
options: .forReplacing,
error: &coordinationError
) { newSource, newDest in
try? FileManager.default.moveItem(at: newSource, to: newDest)
}
if let error = coordinationError {
throw error
}
}
iCloud の URL リソース値
iCloud の状態を確認する
// ✅ Check if file is in iCloud
func isInICloud(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.isUbiquitousItemKey])
return values?.isUbiquitousItem ?? false
}
// ✅ Check download status
func getDownloadStatus(url: URL) -> String {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemDownloadingStatusKey,
.ubiquitousItemIsDownloadingKey,
.ubiquitousItemDownloadingErrorKey
])
if let downloading = values?.ubiquitousItemIsDownloading, downloading {
return "Downloading..."
}
if let status = values?.ubiquitousItemDownloadingStatus {
switch status {
case .current:
return "Downloaded"
case .notDownloaded:
return "Not downloaded (iCloud only)"
case .downloaded:
return "Downloaded"
@unknown default:
return "Unknown"
}
}
return "Unknown"
}
// ✅ Check upload status
func isUploading(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [.ubiquitousItemIsUploadingKey])
return values?.ubiquitousItemIsUploading ?? false
}
// ✅ Check for conflicts
func hasConflicts(url: URL) -> Bool {
let values = try? url.resourceValues(forKeys: [
.ubiquitousItemHasUnresolvedConflictsKey
])
return values?.ubiquitousItemHasUnresolvedConflicts ?? false
}
ファイルをダウンロードする
// ✅ CORRECT: Request download
func downloadFromICloud(url: URL) throws {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
}
// ✅ Monitor download progress
let query = NSMetadataQuery()
query.predicate = NSPredicate(format: "%K == %@",
NSMetadataItemURLKey, url as NSURL)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { notification in
// Check progress
if let item = query.results.first as? NSMetadataItem {
if let percent = item.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as? Double {
print("Downloaded: \(percent)%")
}
}
}
query.start()
競合の解決
競合を検出する
// ✅ Get conflict versions
func getConflictVersions(for url: URL) -> [NSFileVersion]? {
return NSFileVersion.unresolvedConflictVersionsOfItem(at: url)
}
競合を解決する
// ✅ CORRECT: Resolve conflicts
func resolveConflicts(at url: URL, keepingVersion: ConflictResolution) throws {
guard let conflicts = NSFileVersion.unresolvedConflictVersionsOfItem(at: url),
!conflicts.isEmpty else {
return // No conflicts
}
let current = try NSFileVersion.currentVersionOfItem(at: url)
switch keepingVersion {
case .current:
// Keep current version, discard others
for conflict in conflicts {
conflict.isResolved = true
}
case .other(let chosenVersion):
// Replace current with chosen conflict version
try chosenVersion.replaceItem(at: url, options: [])
chosenVersion.isResolved = true
// Mark other conflicts as resolved
for conflict in conflicts where conflict != chosenVersion {
conflict.isResolved = true
}
case .manual:
// App merges manually, then marks resolved
let mergedData = mergeConflicts(current: current, conflicts: conflicts)
try mergedData.write(to: url)
for conflict in conflicts {
conflict.isResolved = true
}
}
// Remove resolved versions
try NSFileVersion.removeOtherVersionsOfItem(at: url)
}
enum ConflictResolution {
case current
case other(NSFileVersion)
case manual
}
NSUbiquitousKeyValueStore(環境設定の同期)
小規模な環境設定専用です(合計 <1 MB、<1024 個のキー)
// ✅ CORRECT: Sync small preferences
let store = NSUbiquitousKeyValueStore.default
// Set values
store.set(true, forKey: "darkModeEnabled")
store.set(2.0, forKey: "textSizeMultiplier")
store.set(["en", "es"], forKey: "selectedLanguages")
// Synchronize
store.synchronize()
// Read values
let darkMode = store.bool(forKey: "darkModeEnabled")
let textSize = store.double(forKey: "textSizeMultiplier")
// Listen for changes from other devices
NotificationCenter.default.addObserver(
forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
object: store,
queue: .main
) { notification in
// Update UI with new values
updatePreferences()
}
制限事項:
- ストレージの合計:1 MB
- キーの最大数:1024
- 値の最大サイズ:1 MB
- 環境設定にのみ使用し、データには使用しないでください
エンタイトルメント
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.com.example.app</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)com.example.app</string>
よくあるパターン
パターン 1:ドキュメントピッカーとの統合
// ✅ Present iCloud document picker
import UniformTypeIdentifiers
let picker = UIDocumentPickerViewController(
forOpeningContentTypes: [.pdf, .plainText]
)
picker.delegate = self
picker.allowsMultipleSelection = false
// Enable iCloud
picker.directoryURL = getICloudContainerURL()
present(picker, animated: true)
パターン 2:ディレクトリの変更を監視する
// ✅ Monitor iCloud directory
class ICloudMonitor {
let query = NSMetadataQuery()
func startMonitoring(directory: URL) {
query.predicate = NSPredicate(format: "%K BEGINSWITH %@",
NSMetadataItemPathKey, directory.path)
query.searchScopes = [NSMetadataQueryUbiquitousDataScope]
NotificationCenter.default.addObserver(
forName: .NSMetadataQueryDidUpdate,
object: query,
queue: .main
) { [weak self] _ in
self?.processResults()
}
query.start()
}
func processResults() {
for item in query.results {
if let metadataItem = item as? NSMetadataItem,
let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL {
print("File: \(url.lastPathComponent)")
}
}
}
}
クイックリファレンス
| タスク | API | 備考 |
|---|---|---|
| iCloud URL を取得 | FileManager.default.url(forUbiquityContainerIdentifier:) | 利用できない場合は nil を返します |
| iCloud 内にあるか確認 | .isUbiquitousItemKey リソース値 | Bool |
| ファイルをダウンロード | startDownloadingUbiquitousItem(at:) | 非同期。NSMetadataQuery で監視します |
| ダウンロード状態を確認 | .ubiquitousItemDownloadingStatusKey | current/notDownloaded/downloaded |
| 競合を確認 | .ubiquitousItemHasUnresolvedConflictsKey | Bool |
| 競合を解決 | NSFileVersion.unresolvedConflictVersionsOfItem(at:) | 手動でマージするか、バージョンを選択します |
| 環境設定を同期 | NSUbiquitousKeyValueStore.default | 合計 <1 MB |
| ファイル調整 | NSFileCoordinator | iCloud ファイルでは必ず使用してください |
関連スキル
axiom-storage— iCloud Drive と CloudKit のどちらを選ぶかaxiom-cloudkit-ref— 構造化データの同期用axiom-cloud-sync-diag— iCloud 同期の問題をデバッグする
最終更新日: 2025-12-12 スキルの種類: リファレンス 最低 iOS バージョン: 5.0(基本機能)、8.0(iCloud Drive)、11.0(最新 API)
コメント
Markdown 対応