大多数浏览器和
Developer App 均支持流媒体播放。
-
使用带有 URLSession 的 async/await
探索如何使用 async/await 和 AsyncSequence 在 URLSession 中采用 Swift 并发功能,以及如何运用 Swift 并发概念来改进您的网络代码。
资源
相关视频
WWDC22
WWDC21
-
下载
♪播放重低音音乐♪ ♪ 嗨 我是国晔 我和我的同事真超 负责开发HTTP框架 我相信目前为止 您时常听闻Swift并发这个词 如果您从未听过 立即观看 “认识Swift async/await” 我将直接进入正题 看看async/await 如何与URLSession一起运作 我最喜欢Swift并发的一点是 它可以让程序代码连贯、精简 还支持原生Swift错误处理 网络原先是异步化 而在iOS 15和macOS Monterey中 我们为您在URLSession 导入了一系列新的API 可供您运用Swift并发功能 为了展示我们新的API 画面上是我们正在开发 以采用async/await的app 这是狗狗爱好者专用的照片共享app 我们可以将这些照片加入最爱 这是我们 用来提取狗狗照片的现有程序代码 它在URLSession上用的是 以完成处理程序为基础的轻松方法 程序代码看起来清楚明了 而且能够在我的有限测试中运作 但是它至少有三个错误 让我们深入了解吧 首先 让我们跟着控制流程 我们建立了资料数据工作并使其继续 工作一完成 我们就会进入完成处理程序 我们检查响应 建立图像 这时控制流程就结束了 看来我们在来来回回 那么线程呢? 以这一小段程序代码来说 它出乎意料地复杂 我们总共有三个不同的执行内容 最外层可以在 调用端任意的线程或队列上执行 URLSession工作完成处理程序 会在会话的委托队列上执行 而最后的完成处理程序 是在主要队列上执行 由于编译器在这里无法派上用场 我们必须非常小心 避免任何线程问题 例如数据争用 现在 我发现了错误 completionHandler的调用 未一致调度至主要队列 这可能会成为程序错误 另外 这里缺少了提早返回 如果发生错误 completionHandler 有可能被调用两次 这可能会违反调用端所做的假设 最后 虽然可能不太明显 但是UIImage建立有可能失败 如果数据格式错误 这个UIImage初始化式会回传nil 我们便会调用completionHandler 其中含有nil图像和nil错误 这不是我们所预期的 现在 这是使用async/await的新版本 一切都简单多了 控制流程从上到下保持连贯 我们知道这个函数里的所有程序代码 是在相同的并发背景执行 因此我们无须再担心线程问题 这里 我们对URLSession使用了 新的async数据方法 它暂停了目前的执行内容 而无须进行阻挡 并成功回传数据和响应 或是掷回错误 我们也使用throw关键词 在得到 未预期的回应时 掷回错误 这可让调用端抓出并处理错误 运用Swift原生错误处理 最后 编译器会吠叫 只要我们试图 从此函数回传可选UIImage 原则上 它会强迫我们正确处理nil 画面上是从网络提取数据时 所使用方法的签名 URLSession数据方法 可接受URL或URLRequest 它们等同于现有数据工作的轻松方法 我们也提供上传方法可让您上传数据 或上传档案 它们等同于现有 上传工作的轻松方法 请确保在传送请求前 设定正确的HTTP方法 因为预设方法GET不支持上传 下载方法会将回应本体存为档案 而不是存在内存 不同于下载工作的轻松方法 这些新方法不会自动删除档案 因此请别忘记自己执行这个动作 在本范例中 我们要将档案移至 不同位置以进一步处理 Swift并发的取消作业 会使用URLSession的async方法 其中一种取消方法 是使用并发工作处理 这里我们调用async 来建立并发工作 将两个资源逐一载入 之后我们可以使用工作处理 取消目前执行的作业 请注意 并发工作 与URLSession工作无关联 即使它们共享“工作”这个名称 我们刚刚提到的方法 数据、上传、下载 都会在回传前等着收到整个回应本体 如果我们想要 渐进式接收回应本体呢? 我很荣幸向您介绍 URLSession.bytes方法 它们会在收到回应标题时回传 并将回应本体 作为bytes的AsyncSequence 为了让您了解运作方式 我的同事真超 将示范他在狗狗app中是如何采用的 谢谢你 国晔 嗨 我是真超 我持续在开发狗狗app的新功能 可以显示多少人 将狗狗的照片加入最爱
现在 我可以将滚动视图下拉 来重新整理最爱计数 我想要实时更新这些最爱计数 如此一来就能提升app互动性 为了实现此目标 我们的后端工程师已建立 实时事件端点 提供我们照片的实时更新内容 我要前往端点 来检视回应 每行响应本体都是一段JSON数据 说明了照片的更新内容 例如更新的最爱计数 让我们使用新的AsyncSequence API 使用端点的响应 并在剖析实时事件时更新最爱计数 我们可以在onAppearHandler函数中 开始实时更新 这个动作会在 照片集合视图出现时调用 在函数内 我要调用新的URLSession.bytes API 从我们新的端点提取数据
请注意 此处回传的bytes 类型为URLSession.AsyncBytes 这可以让我们渐进式使用 回应本体 我还在此新增了错误检查 确保我们从服务器获得成功的回应
我们要将每行回应 作为一段JSON数据剖析 若要这么做 我们可以在AsyncBytes使用行方法
这可让我们收到数据时 逐行使用回应
在循环内 我可以直接剖析JSON数据 并调用updateFavoriteCount 来更新我的用户界面 请注意 用户界面更新 必须在主要动作项目上执行 这就是为什么我要使用await语法 调用updateFavoriteCount 也就是async函数 好极了 现在这些最爱计数已实时更新 交还给你吧 国晔
真超刚刚让我们看到了如何使用 AsyncSequence内建转换 这些行可以用来逐行剖析响应本体 AsyncSequence支持许多轻松的转换 您还可以一起使用AsyncSequence 和其他系统框架API 如FileHandle 若要深入了解AsyncSequence 我推荐您观看影片 “认识AsyncSequence” URLSession是以委托模型设计 可为事件提供回调函数 例如 验证挑战、矩阵 还有更多功能 新的async方法不会再公开基础工作 那么我们该如何处理某个工作 特定的验证挑战呢? 是的 所有这些方法都能涵盖额外的自变量 亦即工作特定委托 可让您提供对象 来处理数据、上传、下载 或字节作业特定的委托信息 我们也在Objective-C的 NSURLSessionTask导入了委托属性 让您运用相同的功能 委托会由工作负责处理 直到工作完成或失败 值得注意的是 背景URLSession 不支持工作特定委托 若会话委托和工作委托 皆已建置方法 则系统会调用工作委托上的方法 现在 真超将为我们展示 如何使用工作特定委托 来处理验证挑战 谢谢你 国晔 我们的狗狗app 具有简易的数据提取层 它是使用新的async API写入的 针对一些数据提取工作 例如将照片标记为最爱 或提取所有加入最爱的照片 用户需要经过验证 现在 当我点击照片来加入最爱 我会收到“未授权”错误 我们来了解该如何使用工作特定委托 新增用户验证 首先 写入URLSessionTaskDelegate 我们将它 称为AuthenticationDelegate吧 AuthenticationDelegate会符合 URLSessionTaskDelegate协定 并会在其初始化式中 接受signInController实例 我们建置的 signInController类别已经包含 实用的helper函数 可让我们用来 提示用户进行认证 接着 我们来建置URLSession didReceive challenge委托方法
在委托方法内 我们可以选择响应 HTTP基础验证挑战 只要提示用户进行认证即可 当然 我们不能忘记错误处理 现在将AuthenticationDelegate类别 作为我们的工作特定委托 若要执行此动作 我可以直接将其实例进行实例化 并作为URLSession.data方法的 委托参数进行剖析 请注意 委托对象不是实例变量 且会由工作负责处理 直到工作完成或失败 此处的新功能在于委托可以用来 处理URLSession工作实例 特定的事件 这点非常方便 当委托方法内的逻辑 只能套用至特定URLSession工作 而无法套用于其他部分 太好了 现在 当我们点击照片来加入最爱
就会弹出登入窗体
登入后 照片便会显示已加入最爱 并且已新增至最爱照片集合 交还给你吧 国晔 真超 谢谢你的示范 我们等不及让您试一试 URLSession与async/await 我们推荐您套用相同的async概念 来改善您的程序代码 包含变更函数 将完成处理程序变更为async函数 并将重复事件处理程序 变更为AsyncSequence 若要了解更多URLSession的改进 我们有一部影片是关于 前卫的全新instrument 负责检验您app的HTTP流量 还有一部影片是关于 URLSession中的HTTP/3支持 谢谢大家 祝您有个愉快的WWDC体验 ♪
-
-
2:52 - Fetch photo with async/await
// Fetch photo with async/await func fetchPhoto(url: URL) async throws -> UIImage { let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw WoofError.invalidServerResponse } guard let image = UIImage(data: data) else { throw WoofError.unsupportedImage } return image }
-
3:45 - URLSession.data
let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 /* OK */ else { throw MyNetworkingError.invalidServerResponse }
-
4:03 - URLSession.upload
var request = URLRequest(url: url) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 201 /* Created */ else { throw MyNetworkingError.invalidServerResponse }
-
4:21 - URLSession.download
let (location, response) = try await URLSession.shared.download(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 /* OK */ else { throw MyNetworkingError.invalidServerResponse } try FileManager.default.moveItem(at: location, to: newLocation)
-
4:44 - Cancellation
let task = Task { let (data1, response1) = try await URLSession.shared.data(from: url1) let (data2, response2) = try await URLSession.shared.data(from: url2) } task.cancel()
-
7:53 - asyncSequence demo
let (bytes, response) = try await URLSession.shared.bytes(from: Self.eventStreamURL) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw WoofError.invalidServerResponse } for try await line in bytes.lines { let photoMetadata = try JSONDecoder().decode(PhotoMetadata.self, from: Data(line.utf8)) await updateFavoriteCount(with: photoMetadata) }
-
11:20 - task specific delegate demo
class AuthenticationDelegate: NSObject, URLSessionTaskDelegate { private let signInController: SignInController init(signInController: SignInController) { self.signInController = signInController } func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) { if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic { do { let (username, password) = try await signInController.promptForCredential() return (.useCredential, URLCredential(user: username, password: password, persistence: .forSession)) } catch { return (.cancelAuthenticationChallenge, nil) } } else { return (.performDefaultHandling, nil) } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。