ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
URLSessionにおけるasync/awaitの使用
async/awaitとAsyncSequenceを使用して、URLSessionでSwift並列処理を採用する方法と、Swift並列処理の概念を適用してネットワーキングのコードを改良する方法を紹介します。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
♪ (URLSessionにおける async/awaitの使用) こんにちは Guoyeです Zhenchaoと私はHTTP フレームワークに取り組んでいます Swiftの同時実行性については 今迄に沢山耳にしたことでしょう まだなら「Swiftのasync/awaitについて」を チェックしてみてください ここでは async/awaitが URLSessionでどのように 機能するかを説明します Swiftの並列処理で 最も気に入っているのは コードを線形で簡潔にして Swiftのネイティブなエラー 処理をサポートしていることです ネットワークは本質的に 非同期であり iOS 15と macOS Montereyでは Swiftの同時実行機能を 利用するための 新しいAPIセットを URLSessionに導入した 新しいAPIを紹介するために async/awaitを採用する ために開発中のAppを紹介します 犬好きのための写真共有Appで これらの写真をお気に入りに 入れることができます これは犬の写真を取得する 既存のコードです これは completionHandler ベースの便利なメソッドを URLSessionで使用 コードは単純なようで 私の限られたテストでは うまくいきました しかし 少なくとも3つの間違いがあります ちょっと探ってみましょう まずコントロールフロー を見ていきましょう データタスクを作成して 再開します タスクが完了したら 完了ハンドラーにジャンプして レスポンスをチェックし イメージを作成して そこでコントロールフローが 終了します うん 前後にジャンプしています スレッディングについては どうですか この小さなコードでは驚くほど 複雑です 実行コンテキストは全部で 3つあります 最上位層は呼び出し元の スレッドまたはキューで実行され URLSessionTask completionHandlerは セッションの委任キューで 実行され 最終的な完了ハンドラは メインキューで実行されます コンパイラはここでは 役に立たないので データ競合などのスレッド化の 問題を避けるために 細心の注意を払う必要があります 今 何かおかしいことに気づきました completionHandlerへの 呼び出しは 常にメインキューにディスパッチ されるわけではありません これはバグである可能性があります また こちらに早く戻ることができません 問題が起こった場合2回呼出し可能 completionHandlerを これは呼び出し元が行った 仮定に違反する可能性があります 最後にこれはあまり明白では ないかもしれませんが UIImageの作成は 失敗する可能性があります データの形式が正しくない場合 UIImageイニシャライザーは nilと返すため completionHandlerを nil imageと nil errorの 両方で呼び出します これは想定外です これはasync/awaitを 使った新しいバージョンです わあ もっとシンプルだ コントロールフローは 上から下まで直線的であり この関数のすべてが同じ 並行性コンテキストで 実行されることがわかっているため スレッド化の問題を心配する 必要はありません ここではURLSessionで 新規非同期データ方法を使用した 現在の実行コンテキストを中断し ブロックせずに 正常終了時にデータとレスポンスを 返すか エラーをスローします また応答が予期しない場合に エラーをスローするために スローキーワードも使用しました これで呼び出し側はSwiftの ネイティブなエラー処理を 使用してエラーをキャッチし 処理することができます 最後に この関数からオプションの UIImageを返そうとすると コンパイラが吠えるので基本的には nilを正しく処理する必要がある nilを正しく扱うことを 強制します ここでは ネットワークから データを取得するために使用した メソッドのシグネチャを示します URLSession.data方法は URLまたはURLRequestのどれかを受入 これらは既存のデータタスクの 簡易メソッドと同等です また データをアップロードしたり ファイルをアップロードしたり できるアップロードメソッドも 用意されています これらは 既存のアップロード タスクの 簡易メソッドと同等です デフォルトのメソッドGETは アップロードをサポート していないため リクエストを送信する前に 正しいHTTPメソッドを 設定してください ダウンロードメソッドは レスポンス本文をメモリー ではなくファイルとして格納します ダウンロードタスクの 便利な方法とは異なり これらの新しい方法ではファイルが 自動的に削除されないため 自分で削除することを 忘れないでください この例では さらに処理するために ファイルを別の場所に移動します Swiftの同時実行性 キャンセルは URLSessionで動作する asyncメソッド 取消する一つの方法は 同時並行タスクの Task.Handleを使用すること ここではasyncを呼び出して 2つのリソースを1つずつ ロードする同時実行 タスクを作成します その後 Task.Handleを使って 現在実行中の操作を キャンセルすることができます 並行性タスクに注意してください 「タスク」という名前を 共有していますが URLSessionTaskとは 無関係です 先ほど説明したメソッド データ アップロード ダウンロードは レスポンス本文全体が到着 するのを待ってから戻ります レスポンス本文を段階的に受信する 場合はどうすればよいですか URLSession.bytes メソッドを紹介します レスポンスヘッダが受信された 時に戻りレスポンスボディをバイト AsyncSequenceとして 配送します 同僚のZhenchaoが Dogs Appにどうやってそれを 適用しているかをデモします ありがとうGuoye こんにちは Zhenchaoです 犬の写真をどれだけの人が気に 入ったかを表示する Dogs Appの新機能に 取り組んでいます
現時点では スクロールビューをプルダウンして お気に入りの数を更新できます これらのお気に入りの数を リアルタイムで更新したいです そうすることでAppはずっと インタラクティブに感じられる そのために バックエンドのエンジニアたちは リアルタイムのイベント エンドポイントを作って 写真をリアルタイムで アップデートできるようにした エンドポイントを調べて レスポンスを調べます レスポンス本文の各行は 写真の更新 更新されたお気に入り数など 記述するJSONデータです 新規非同期シーケンスAPIを使用 してエンドポイントのレスポンスを 消費しリアルタイムイベントの 解析時にお気に入りのカウントを 更新しましょう onAppearHandler 関数でライブ更新を開始できます この関数は 写真コレクションビューが 表示されたときに呼び出されます この関数の中で 新しい URLSession.bytes APIを呼び出して 新しいエンドポイントから データを取得します
ここで返されるバイトのタイプは URLSession.AsyncBytes であることに注意してください これにより レスポンスボディを インクリメンタルに消費 することができます サーバーから正常なレスポンスを 得ていることを確認するために エラーチェックを追加しました
レスポンスの各行を JSONデータの一部 として解析したい その為にAsyncBytesの AsyncBytesのlines方法を使用
これによりデータを受信するたびに 一行ずつレスポンスを 消費することができます
ループ内では JSONデータを解析して UIを更新するために updateFavoriteCount UI の更新はメインのアクターで 行われる必要があることに 注意してください そのため非同期関数である updateFavoriteCount を await 構文で呼び出している ナイス お気に入りのカウントは リアルタイムで更新されます Guoye戻します
Zhenchaoは AsyncSequence 組み込み変換 linesを使用して応答本文を 1行ずつ解析する方法を示しました AsyncSequenceは多くの 便利な変換をサポートしており FileHandleなどの他の システムフレームワークAPIでも AsyncSequenceを 使用できます AsyncSequenceの 詳細については動画 「AsyncSequenceについて」を ご覧ください URLSessionは 認証チャレンジやメトリックなど イベントのコールバックを提供する 委任モデルを中心に 設計されています 新しい非同期方式では基礎となる タスクが公開されなくなったため タスク固有の認証の課題を どのように処理するのでしょうか はい これらのメソッドはすべて 追加の引数 タスク固有の デリゲートを取ることができます このデータアップロードに特化した デリゲートメッセージを処理する オブジェクトを提供できます ダウンロードやバイトの操作を 行うことができます またObjective-Cでは NSURLSessionTask プロパティを導入し 同じ機能を利用できます デリゲートはタスクが完了するか 失敗するまで タスクによって強く拘束されます なお タスク固有のデリゲートは バックグラウンドの URLSessionでは サポートされていません ある方式がセッションデリゲートと タスクデリゲートの両方に 実装している場合は タスク デリゲートの方法が呼出されます Zhenchaoはタスク固有の デリゲートを使って 認証のチャレンジを 処理する方法を紹介します ありがとうGuoye Dogs Appには 新しい非同期APIで書かれた シンプルなデータフェッチレイヤー があります 写真をお気に入りに登録したり お気に入りに登録された写真を すべて取得するなど データを取得するためには ユーザーの認証が必要です 現在 写真をお気に入りに 登録しようとタップすると 「Unauthorized」 というエラーが表示されます タスク固有のデリゲートを 使用してユーザー認証を 追加する方法について説明します URLSessionTaskDelegateを まず作成します AuthenticationDelegate と呼びましょう
AuthenticationDelegate は URLSessionTaskDelegate プロトコルに準拠し そのイニシャライザーで signInControllerの インスタンスを受け入れます 実装したsignInController クラスには ユーザーにクレデンシャルを 要求するのに利用できる 便利なヘルパー関数がすでに 含まれています URLSession idReceive challenge delegateメソッドを 次に実装します
デリゲート方式では 認証情報の入力をユーザーに 求めることで HTTP基本認証の要求に 応答することを選択できます もちろん エラー処理を忘れてはいけません AuthenticationDelegate クラスを次にタスク固有の デリゲートとして使用します そのためにはそのインスタンスを インスタンス化し それを方式 URLSession.dataで delegate引数として 構文解析するだけです なお デリゲートオブジェクトは インスタンス変数ではなく タスクが完了するか失敗するまで タスクに強く保持されています ここでの新機能は デリゲートを使って URLSessionタスクの インスタンスに固有の イベントを処理できることです URLSessionタスクの インスタンスに固有の イベントを扱うことができます 素晴らしい ここをタップして 写真をお気に入りに登録できます
...ログインフォームが表示されます
ログインすると その写真が お気に入りと表示され お気に入りの写真コレクションに 追加されました Guoyeに戻します デモをありがとうZhenchao URLSessionで async/awaitを 皆さんが試すのを待つことは できませんし 関数の変更完了 ハンドラのasync関数への変更 繰り返し イベントハンドラの AsyncSequencesへの 変更など 同じasyncの概念を適用した コードの改善をお勧めします URLSessionの 進歩についてさらに学ぶために 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) } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。