WWDC Quick Look 💓 By SwiftGGTeam
Use async/await with URLSession

Use async/await with URLSession

元の動画を見る

ハイライト

iOS 15 では新しい URLSession が追加されますdata(for:)upload(for:from:)download(from:)bytes(from:)他のネイティブ非同期メソッドは、タスク固有のデリゲートによる認証チャレンジの処理をサポートする一方で、応答本文を段階的に受信するための AsyncSequence を導入しています。

主な内容

URLSession の非同期/待機バージョンを導入する理由は何ですか?

一般的なネットワーク要求コードには、完了ハンドラーを使用する場合に 3 つの問題があります。

  1. 制御フローが飛び回ります: タスクの作成 → 再開 → completedHandler にジャンプ → メインキューの completedHandler にジャンプ
  2. スレッド セーフティ トラップ: 3 つの実行コンテキスト (呼び出し元キュー、セッション デリゲート キュー、メイン キュー) が関係しており、コンパイラはデータ競合の検出には役立ちません。
  3. エラー処理の難しさ: completedHandler が 2 回呼び出される可能性があります。UIImage(data:)オプションですが、対応するエラーを返しません

async/await に変更すると、制御フローは上から下に直線的に実行されます。すべてのコードは同じ同時実行コンテキストで実行されます。スレッドの問題や誤った使用を心配する必要はありません。throw処理中、コンパイラはオプションの処理を強制します。

URLSession の非同期メソッド

iOS 15 と macOS Monterey の新しい非同期メソッドには次のものがあります。

  • data(from:) / data(for:):データを入手します。コンビニの利便性はとても便利です。
  • upload(for:from:): データまたはファイルのアップロード、アップロードタスクに相当
  • download(from:) / download(for:): ファイルをディスクにダウンロードします。自動的には削除されません。手動で移動または削除する必要があります。
  • bytes(from:) / bytes(for:):戻るAsyncBytes、応答本文を段階的に受信するために使用されます

すべてのメソッドはネイティブ Swift をサポートしますtry-catchエラー処理とタスクのキャンセル。

増分応答の処理: URLSession.bytes

datauploaddownloadこれらのメソッドはすべて、完全な応答本文が到着するまで待機してから戻ります。増分処理が必要な場合 (SSE、ストリーミング JSON など)、次を使用します。bytes方法。

bytes(from:)応答ヘッダーが到着した直後に戻り、応答本文は次で終わります。AsyncSequence<UInt8>フォームの増分配信。AsyncBytes供給lines応答本文を行ごとに解析する変換。

Task-specific Delegate

URLSession のデリゲート モデルは、認証チャレンジやメトリクスなどのコールバックを提供します。新しい非同期メソッドは、基礎となるタスクを公開しなくなりました。特定のタスクの認証チャレンジをどのように処理するか?

答えは、タスク固有のデリゲート パラメーターを async メソッドに渡すことです。このデリゲートは、その特定のタスクのイベントのみを処理し、完了または失敗するまでタスクによって強く参照されます。

セッション デリゲートとタスク デリゲートの両方が同じメソッドを実装する場合、タスク デリゲートの実装が優先されます。注: バックグラウンド URL セッションは、タスク固有のデリゲートをサポートしません。

Swift 同時実行キャンセルメカニズム

Task種類は次のとおりです。cancel()ただし、Swift Concurrency のことに注意してください。TaskそしてURLSessionTaskこれらは名前が似ているだけで、まったく異なる概念です。

キャンセルは協力的です - 電話してくださいcancel()その後、コードは次の一時停止ポイントになります (例:await) キャンセルステータスを確認してスローしますCancellationError

詳細

写真の非同期バージョンを取得する

02:52

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
}

キーポイント:

  • data(from:)戻る(Data, URLResponse) 元组
  • 使用guardステータスコードが一致しない場合は、ステータスコードを確認してください。throw間違い
  • UIImage(data:)戻り値はオプション、処理する必要がありますnil状態
  • 整个函数是 async throws、電話するときに使用しますtry await

URL データメソッドのリクエストバージョン

03:45

let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 else {
    throw MyNetworkingError.invalidServerResponse
}

キーポイント:

  • data(for:)受け入れるURLRequestパラメータでは、HTTP ヘッダー、メソッドなどを構成できます。
  • 応答チェックモードは同じです: ガードキャスト + ステータスコードチェック

ファイルをアップロードする

04:03

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 else {
    throw MyNetworkingError.invalidServerResponse
}

キーポイント:

  • upload(for:fromFile:)ディスク ファイルからアップロード、大きなファイルに適しています
  • アップロードが成功すると、通常、201 Created が返されます。対応するステータス コードを確認してください。
  • 也有upload(for:from:)バージョン、からDataアップロード

ファイルをダウンロードする

04:21

let (location, response) = try await URLSession.shared.download(from: url)
guard let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 else {
    throw MyNetworkingError.invalidServerResponse
}

try FileManager.default.moveItem(at: location, to: newLocation)

キーポイント:

  • download(from:)手動でターゲットの場所に移動する必要がある一時ファイルの URL を返します。
  • 古いバージョンの便利なメソッドではファイルが自動的に削除されますが、新しいバージョンでは削除されません。
  • 処理完了後は必ず一時ファイルを削除または移動してください。

タスクがキャンセルされました

04:44

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()

キーポイント:

  • Task { }構造化された同時実行タスクを作成し、返すTask.Handle
  • cancel()次はawaitクリックして有効にし、投げますCancellationError
  • 最初の場合awaitが返された場合、キャンセルは 2 番目のリクエストには影響せず、両方のシリアル リクエストが完了します。

応答本文を増分受信する

07:53

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)
}

キーポイント:

  • bytes(from:)戻る(URLSession.AsyncBytes, URLResponse)タプル
  • AsyncBytes.linesによると\nレスポンスボディを分割して返すAsyncSequence<String>
  • for try awaitSSE およびストリーミング JSON シナリオに適したループ内の各行を処理します。
  • await updateFavoriteCount別の非同期関数を呼び出して UI を更新する

タスク固有のデリゲートが認証を処理します

11:20

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)
        }
    }
}

使用方法:

let delegate = AuthenticationDelegate(signInController: signInController)
let (data, response) = try await URLSession.shared.data(for: request, delegate: delegate)

キーポイント:

  • デリゲート メソッド自体は次のようにすることができます。async、待機呼び出しをサポート
  • デリゲートはタスクによって強く参照されるため、手動で保持する必要はありません
  • HTTP 基本認証の場合、資格情報を取得するためにログイン ボックスがポップアップ表示されます。
  • 戻る(useCredential, credential)認証情報を使用してリクエストを続行するようにセッションに指示します。

重要ポイント

  1. 既存のネットワーク層を非同期/待機に移行します
  • 何をすべきか: すべての完了ハンドラーベースのネットワーク リクエスト メソッドを非同期スロー バージョンに変更します。
  • 実行する価値がある理由: コードの可読性が大幅に向上し、エラー処理がより安全になり、スレッド セーフティのバグが減少します。
  • 開始方法: 最も一般的に使用されるリクエスト メソッドから開始し、completionHandler: @escaping (Result<T, Error>) -> Voidに変更しますasync throws -> T。エントリーAPIはURLSession.data(for:)
  1. リアルタイム更新インターフェイス用のバイト API へのアクセス
  • やるべきこと: を使用しますURLSession.bytes(from:)SSE/ストリーミング インターフェイスの代替ポーリングまたは WebSocket 処理
  • 実行する価値がある理由: 応答本文を段階的に受信し、メモリ使用量を削減し、遅延を小さくします。
  • 開始方法: 既存の SSE インターフェイスを確認します。bytes.lines行くごとに分析しますfor try await各メッセージをループで処理する
  1. タスク固有のデリゲートを使用して認証ロジックを簡素化
  • 何をすべきか: グローバル デリゲートでタスク ID を使用してディスパッチする代わりに、特別な認証を必要とするリクエスト用の専用のデリゲート クラスを作成します。
  • 実行する価値がある理由: デリゲート ロジックとリクエスト ロジックを一緒にすると、保守が容易になります。
  • 開始方法: インストールURLSessionTaskDelegate、存在するdata(for:delegate:)インスタンスを渡します。デリゲート メソッドは次のようにマークできます。async
  1. ファイルのダウンロード時のメモリ使用量を最適化
  • 内容: 大きなファイルのダウンロードに使用しますdownload(from:)の代わりにdata(from:)
  • 実行する価値がある理由:data応答本文全体をメモリにロードします。download一時ファイルに書き込み、メモリ使用量は一定
  • 開始方法: すべての写真とビデオのダウンロードを次のように変更します。download, ダウンロード完了後にご利用くださいFileManager.moveItem目的の場所に移動

関連セッション

コメント

GitHub Issues · utterances