作者:Drew McCormack (Mastodon: @drewmccormack@mastodon.cloud)
一个易于使用的 Swift 封装器,用于 iCloud Drive。
SwiftCloudDrive 处理了诸如文件协调、访问安全作用域资源、文件冲突和云元数据查询等复杂性,从而为在 iCloud 中处理文件提供了直接的异步函数。它使得在云端处理文件几乎和使用本地 FileManager
一样简单。
对于 iCloud 的高级用途,您可能应该直接使用 Apple 的框架。 这让您拥有最大的控制权,但代价是学习曲线陡峭。
例如,如果您需要完全控制 iCloud Drive 中的文件何时下载到设备,或者拥有像 Apple 的 Photos 这样的应用程序,在用户请求之前通常希望将文件保留在云中,则不应使用 SwiftCloudDrive。
如果您希望将应用的所有 iCloud Drive 文件都存储在设备上以及云中,SwiftCloudDrive 可以帮助您更快地启动和运行。 您不必担心文件协调、元数据查询或冲突解决,这些都是使用 iCloud Drive 的一部分。 SwiftCloudDrive 将为您提供一个简单的类来上传、下载、查询和更新,其工作方式与您已经熟悉的 FileManager
类型非常相似。
要开始使用,请将 SwiftCloudDrive 包添加到您的 Xcode 项目,然后在 Xcode 中应用的 Target 的 Signing & Capabilities 选项卡中启用 iCloud Drive。 您可以接受默认容器标识符,也可以选择自定义标识符。
如果您使用的是默认的 iCloud 容器,那么在您的应用程序源代码中设置 CloudDrive
可以简单到如下所示:
import SwiftCloudDrive
let drive = try await CloudDrive()
如果您有自定义的 iCloud 容器,只需传入标识符即可。
let customDrive = try await CloudDrive(storage: .iCloudContainer(containerIdentifier: "iCloud.com.yourcompany.app"))
在上述情况下,您将直接访问容器根目录中的文件,但您也可以将 CloudDrive
锚定到容器的特定子目录,如下所示:
let subDrive = try await CloudDrive(relativePathToRootInContainer: "Sub/Directory/Of/Choice")
如果目录不存在,CloudDrive
将为您创建该目录。
一旦你拥有了一个 CloudDrive
对象,你就可以像使用 FileManager
一样查询文件状态。 但有一个很大的区别:由于文件可能是远程的,并且必须下载,所以所有的函数调用都是 async
的。
要确定目录是否存在,您需要这样做:
let path = RootRelativePath(path: "Path/To/Dir")
let exists = try await drive.directoryExists(at: path)
如果您想知道特定文件是否存在,您可以使用:
let exists = try await drive.fileExists(at: path)
正如您在前一节中看到的,RootRelativePath
结构用于引用相对于 CloudDrive
根目录的路径。
如果您经常使用特定的固定路径,扩展 RootRelativePath
来定义静态常量会很有用。
extension RootRelativePath {
static let images = Self(path: "Images")
}
let imagesExist = try await drive.directoryExists(at: .images)
let dogImageExists = try await drive.fileExists(at: .images.appending("Dog.jpeg"))
正如您所看到的,RootRelativePath
还定义了一个 appending
函数,这使得扩展现有路径变得简单。
在云中创建目录同样简单。 请注意,如果缺少中间目录,也会创建这些目录。
try await drive.createDirectory(at: .images)
要将文件移动到云中,您可以将本地文件“上传”到容器,或者您可以将 Data
直接写入文件。
let data = "Some text".data(using: .utf8)!
try await drive.writeFile(with: data, at: .root.appending("file.txt"))
在本例中,我们使用内置的静态常量 root
来构建路径,但我们也可以只使用 RootRelativePath("file.txt")
。
要从云容器外部上传文件,您可以使用如下代码:
try await drive.upload(from: "/Users/eddy/Desktop/image.jpeg", to: .images.appending("image.jpeg"))
一旦您在云中拥有一个文件,您可以通过再次上传来更改它,但您必须首先删除旧版本,否则会发生错误。
let cloudPath: RootRelativePath = .images.appending("image.jpeg")
try? await drive.removeFile(at: cloudPath)
try await drive.upload(from: "/Users/eddy/Desktop/image_new.jpeg", to: cloudPath)
或者,您可以写入内容,而无需先删除现有文件。
try await drive.writeFile(with: newImageData, at: cloudPath)
如果您需要更精细的控制,您可以进行任何您喜欢的就地更新,如下所示:
try await drive.updateFile(at: imagePath) { fileURL in
// Make any changes you like to the file at `fileURL`
// You can also throw an error
}
正如我们已经看到的,您可以非常轻松地删除文件和目录。
try await drive.removeFile(at: cloudFilePath)
try await drive.removeDirectory(at: cloudDirPath)
如果遇到错误的项类型,则删除失败并抛出错误。
在使用远程设备上的更改时,了解何时下载了新文件或已应用更新非常重要。 您可以为此目的注册 CloudDriveObserver
。
class Controller: CloudDriveObserver {
func cloudDriveDidChange(_ drive: CloudDrive, rootRelativePaths: [RootRelativePath]) {
// Decide if data needs refetching due to remote changes,
// or if changes need to be applied in the UI
}
}
let controller = Controller()
drive.observer = controller
请注意,传递给观察者的相对路径不一定是新文件,也可能是具有更新的文件,甚至是已删除的文件。 您的代码应考虑到这些可能性,并进行适当调整。