大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 FileProvider 将桌面级同步功能带入 iOS
探索在创建“文件提供程序”扩展时,如何在您的 iPhone 和 iPad App 内更快速、更高效地同步文件。与 File Provider 团队保持同步,了解如何为 iOS 构建现代文件提供程序。我们将向您展示如何架构 App,以支持无缝文件同步、上传和下载。我们还将探索如何实现无状态,并增强您的文件提供程序以防意外情况。为了充分利用好本次讲座,我们建议您先在 macOS 上体验一下文件提供程序。
资源
- File Provider
- File Provider UI
- Sending notification requests to APNs
- Synchronizing files using file provider extensions
相关视频
WWDC21
-
下载
大家好 我叫 Johannes Fortmann 来自 Cloud File Provider 团队 今天 我们将讨论如何 使用 iOS 16 中新引入的 File Provider API 从而将桌面级同步功能带入 iOS 介绍完之后 我们会快速回顾一下 文件提供程序的目标 我们将讨论 架构 App 的 最优方法 以及一些对 iOS 尤为重要的 最佳做法 最后 我将向大家快速演示一个 在 iOS 上运行的提供程序 Big Sur 推出了一个 声明式 API 用于将文件同步到 Mac 该 API 已被许多云供应商 采用 并且取得了 巨大成功 我的团队一直在稳步 改进这个 API 我们很高兴 也能在 iOS 16 上使用它 这一 API 将使您的 iOS App 能够提供我们所说的 “桌面级同步”功能 具体而言 随着 iOS 上的 App 功能越来越强大 它们要能够访问 文件系统上的共享位置 这一点非常重要 人们希望这些功能强大的 App 可以访问 所有类型的文件系统 对象 他们还应该能够选择让 App 访问文件夹 并创建新文件 我们当然希望在保证一致性的同时 实现所有这些目标 这里的一致性是什么意思呢? 在 iOS 上 由于电源方面的问题 后台运行时曾一直受到限制 与此同时 后台还会不断收到 上传的更改内容 为了解决这个问题 新的 File Provider API 应运而生 在基本层面上 当您实现一个 App 扩展时 该扩展负责 枚举项目、 获取并上传内容 并在项目发生远程更改时 更新项目列表 而系统负责 公开您所提供的信息 并保持一致性 系统的一项重要任务 就是跟踪错误 并在必要时重试 对于某些操作 例如获取内容 重试是很自然的事 用户正在热切地 等待下载 他们可能会紧盯着 进度条 另一方面 上传需要进行调度 通过跟踪 磁盘上项目的状态 系统可确保 上传的是更新后的内容 系统会跟踪进度和错误 并在必要时 重新上传 另一个较为复杂的主题 涉及到上传期间的一致性 系统会管理 文件内容的克隆 以确保在上传过程中 可成功完成对文件的 后续访问并显示 正确的数据 在这些操作过程中 系统还要确保 本地版本 保持一致 哪怕有多个 App 访问它 同时还要注意远程服务器的 下行同步 这是使用 APFS 功能 和文件协调功能 来实现的 并且对您 是透明的 存储容量限制是 移动设备的一个重要约束 系统使用 APFS 功能 来自动跟踪 本地文件的更改状态 它可以 根据磁盘使用情况 和最近不常使用状态 来透明地收回 没有发生本地更改的文件 在“设置”的 “储存空间管理”面板中 完全上传的文件不会计入 App 占用的内存 大家可能已经注意到 到目前为止 我们已经讨论了系统 和扩展 现在 让我们来谈一谈 App 发挥作用的地方 我建议大家要严格 区分不同的关注点 系统负责 管理磁盘上的结构 并调度任务 扩展负责 执行这些任务 以实现上行和下行同步 系统会跟踪文件 层次结构的所有状态 以及需要同步的部分 这意味着扩展 可能会非常轻量 它根本不需要跟踪 任何特定于项目的状态 您的 App 不负责任何同步 理想情况下 它根本不需要 与服务器通信 相反 它会 通过两种机制 与扩展交互 一种是 与扩展间接交互 具体方式与系统上任何其他 App 的相同 通过 API 来获取 任何托管项的文件 URL 包括根目录 然后 可以使用常规 文件系统 API 来访问这些位置 另一种是 您的 App 可以请求指向扩展的 直接 XPC 服务 连接 这对于处理那些 无法表示为 文件系统操作的任务 尤为有用 例如共享文件 或解决冲突 File Provider UI 扩展 也可以使用 这两种机制 在“文件”App 中 提供额外的集成点 这里 我想简单谈谈 三点 这些对无状态提供程序来说 尤其重要 首先说一下上传 正如我前面提到的 系统会跟踪 上传 并允许您延长 上传时间 这样做的一个 重要后果是: 您必须通过报告进度 让系统知道您的上传 实际上正在进行 如果上传任务没有进展 它就会被取消 在彻底结束上传之前 系统提供了一个宽限期; 但如果 取消时间过长 您的扩展 将被终止 让我们来看一下代码 要实现 取消处理程序 只需 通过特定于任务的方法 在进度返回值上进行相应设置 对于上传而言 就是 modifyItem 在处理程序中 正在执行的实际上传工作 会被取消 当然 您还需要 调用完成处理程序 来指示 发生了取消错误 这里的代码示例使用了 异步任务取消 这是为了方便起见 但您也可以手动调用 完成处理程序 接下来 让我们来谈一谈 下行同步路径 当用户 与他们的文件交互时 您的主 App 并未运行 也就无法从服务器 接收更改内容 要想通知系统 有远程更改 您应该实现 推送通知 PushKit 为 文件提供程序 使用了特定的推送类型 您可以直接 在扩展上 注册这些推送 在您的服务器上 使用定义明确的 有效负载来发送推送 系统将接收推送 并在适当时刷新 当前状态 与其他类型的 任务一样 系统可能会 根据实际情况 推迟实际刷新 比如根据电池状态 或用户当前是否 正在查看文件 最后一点 我只是 想提醒大家 注意这一点: 系统管理着 扩展所报告的文件夹 层次结构 因此 它可以提供 整个文件夹层次结构 在这里 扩展不需要 做任何额外工作 新的文件提供程序 会默认启用该功能 让我们来快速演示一下 最后一项功能将会带来 什么样的工作流程 我已经用本次讲座的示例代码 对我的设备进行了设置 示例代码已经 移植到 iOS 上 我们构建了一个 iOS App 来处理服务器登录 但这个版本的扩展 与 macOS 版本 基本相同 现在 我正在 iPad 上 运行示例代码 “文件”App 在右侧运行 它已经 同步了我的文件 我还编写了一个 要用到文件夹 选择的 App 该 App 会对文件夹中的 所有图像应用深褐色滤镜 这种类型的应用程序 就会得益于文件夹访问功能 因为它可以 对文件夹中的所有项目进行操作 而无需与每个项目单独交互 借助桌面级同步 我可以方便地将文件夹 从“文件”App 拖到 批处理编辑器中 现在 我来调取 文件夹和文件 以便 监控进度 然后 按下按钮 我的所有照片 都会被下载和修改 修改完成后 它们会自动上传 扩展所报告的 上传进度 将在“文件”App 的底部 向用户显示 让他们一目了然 假设我想让自己的 App 也实现类似的功能 首先 让我们来实现 项目拖动 要允许拖动 需实现 onDrag 方法 该方法将返回 NSItemProvider 针对要拖动的 文件类型 您可以 在 itemProvider 上 注册一个文件表示形式 在本例中 它是一个文件夹 使用 getUserVisibleURL 方法 获取 URL 在接收端 实现 onDrop 将某个视图标记为 放置目标 然后 您可以 从相应的项目提供程序 载入文件 URL 请注意 这是一个 位于沙盒之外的文件 为了让您的 App 能够访问它 这个文件必须使用 并发布 URL 的 安全范围 接下来该怎么做? 我们已经更新了示例代码 其中包含一个 iOS App 下载代码 然后尝试 设置一个 简单的无状态提供程序 如果是从零开始 请确保使用已更新的 Xcode 模板 它包含一个基本框架 可以帮助您轻松入门 要了解 有关文件提供程序 及其实现方式的更多信息 请参考 WWDC21 中的 “使用 macOS 上的 File Provider 将文件同步到云端” 感谢您的观看 我会很高兴 能在 iOS 设备上 使用到您的性能卓越、 安全可靠的提供程序
-
-
6:04 - Implement a Progress Cancellation Handler
// Implementing a progress cancellation handler public func modifyItem(_ item: ..., completionHandler: (..., Error?) -> Void) -> Progress { let progress = Progress() let uploadTask = Task { do { // ... try Task.checkCancellation() // ... } catch let error { completionHandler(nil, [], false, error) } } progress.cancellationHandler = { uploadTask.cancel() } return progress }
-
6:53 - Register for Push Notifications
// Registering for push notifications import PushKit let pushRegistry = PKPushRegistry(queue: queue) pushRegistry.delegate = self pushRegistry.desiredPushTypes = Set([PKPushType.fileProvider]) ... // On the server: push // // { // "container-identifier" = "NSFileProviderWorkingSetContainerItemIdentifier" // "domain" = "<domain identifier>" // } // // with topic "<your application identifier>.pushkit.fileprovider"
-
8:53 - Drag and Drop: Implement Dragging
// Sending out drags var body: some View { Text("🥐") .onDrag { let itemProvider = NSItemProvider() itemProvider.registerFileRepresentation(for: .folder, openInPlace: true) { completionHandler in self.manager.getUserVisibleURL(for: folderItemID) { fileURL, error in guard let fileURL = fileURL else { completionHandler(nil, false, error) return } completionHandler(fileURL, true, nil) } return Progress() } return itemProvider } }
-
9:24 - Drag and Drop: Implement Dropping
// Receiving drops var body: some View { Text("🥬") .onDrop(of: [.folder], isTargeted: $dropTarget) { providers in guard let prov = providers.first(where: { provider in !provider.registeredContentTypes(conformingTo: .folder).isEmpty }) else { return false } prov.loadFileRepresentation(for: .folder, openInPlace: true) { url, inPlace, err in guard let url = url else { return } Task { url.startAccessingSecurityScopedResource() // use URL url.stopAccessingSecurityScopedResource() } } return true } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。