mcpField
A
axiom-icloud-drive-ref

axiom-icloud-drive-ref

github.com/charleswiltgen开发1.2k170 次安装今天更新
代码仓库

关于 axiom-icloud-drive-ref

面向 iOS 开发者的 iCloud Drive 文件同步参考技能,涵盖文件协调、下载状态和冲突处理,也说明何时应改用 CloudKit 或键值存储。

主要功能

  1. 01获取与管理 iCloud 容器文件
  2. 02使用 NSFileCoordinator 协调读写
  3. 03检查文件状态并请求下载
  4. 04检测并解决文件版本冲突
  5. 05同步小型偏好设置

适用场景

  1. 01为文档应用实现跨设备文件同步
  2. 02安全地读写 iCloud 文件
  3. 03处理同步冲突与下载状态
  4. 04同步少量用户偏好设置

安装

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

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-icloud-drive-ref

安装后在 Claude Code 中输入/axiom-icloud-drive-ref确认已加载。

详细介绍

iCloud Drive 参考

用途:使用 ubiquitous 容器进行基于文件的 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)

ubiquitous 容器

获取 ubiquitous 容器 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 URLFileManager.default.url(forUbiquityContainerIdentifier:)不可用时返回 nil
检查是否位于 iCloud 中.isUbiquitousItemKey 资源值Bool
下载文件startDownloadingUbiquitousItem(at:)异步操作,使用 NSMetadataQuery 监视
检查下载状态.ubiquitousItemDownloadingStatusKeycurrent/notDownloaded/downloaded
检查是否存在冲突.ubiquitousItemHasUnresolvedConflictsKeyBool
解决冲突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