WWDC Quick Look 💓 By SwiftGGTeam
Meet AsyncSequence

Meet AsyncSequence

元の動画を見る

ハイライト

AsyncSequenceSwift 5.5で導入されたコアプロトコルであり、同期ですSequenceの非同期同等物。のようにfor item in sequence同期シーケンスをたどるのと同じように、for await item in asyncSequence非同期的に生成された要素を反復処理することができます。

主な内容

存在するasync/await以前は、一連の非同期値を処理するには、コールバック、プロキシ パターン、または Combine Publisher が必要でした。これらのアプローチにはそれぞれ独自の複雑さがあり、ネストされたコールバックは「コールバック地獄」を引き起こし、エージェントは複数のメソッドに分散しており、Combine には急な学習曲線があります。

AsyncSequence最も単純なものを使用するfor-await-inループ処理はこの問題を解決します。これにより、非同期シーケンスの処理が同期シーケンスとほぼ同じように見えます。コンパイラは舞台裏で次のように動作します。for-await-inペアに変換するmakeAsyncIterator()電話をかけ、それを繰り返しますawait iterator.next()

システムフレームワークのAPIが多数提供されているAsyncSequenceインタフェース。URL.linesテキスト ファイルは 1 行ずつ非同期で読み取ることができます。URLSessionbytes(from:)戻るAsyncBytesNotificationCenternotifications(named:)通知シーケンスを返します。FileHandle非同期読み取りもサポートされています。

セッションでも紹介していますAsyncStreamそしてAsyncThrowingStream、従来のコールバック/デリゲート パターンを次のように変換します。AsyncSequence橋。使用できますAsyncStreamTimer と CLLocationManager のデリゲート コールバックをラップします。AsyncSequence

##詳細

AsyncSequence のトラバース

04:28

for await quake in quakes {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

キーポイント:

  • for awaitSwift 5.5 のトラバース用の新しい構文ですAsyncSequence
  • シーケンスでエラーが発生する可能性がある場合は、次を使用します。for try await
  • breakそしてcontinue存在するfor-await-in正常に動作しています
for try await quake in quakeDownload {
    if quake.location == nil {
        break
    }
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

コンパイラーが非同期反復を処理する方法

03:52

// 同步迭代
var iterator = quakes.makeIterator()
while let quake = iterator.next() {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

// 异步迭代
var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

キーポイント:

  • コンパイラfor-inに変換while let + makeIterator() + next()
  • コンパイラfor await-inに変換while let await + makeAsyncIterator() + next()
  • 各反復がハングし、次の値の到着を待機する可能性があります

同時反復

07:15

let iteration1 = Task {
    for await quake in quakes {
        displayEarthquake(quake)
    }
}

let iteration2 = Task {
    do {
        for try await quake in quakeDownload {
            updateMap(with: quake)
        }
    } catch {
        handleError(error)
    }
}

// 后续可以取消
iteration1.cancel()
iteration2.cancel()

キーポイント:

  • バンドルfor-await-inつけるTaskクロージャ内で複数の反復を同時に実行可能
  • Task提供されたcancel()メソッド、反復は外部からキャンセル可能
  • 無限に実行される可能性のあるシーケンス (通知フローなど) の場合、この方法でライフ サイクルを管理できます。

FileHandle からバイトを読み取ります

07:56

for try await line in FileHandle.standardInput.bytes.lines {
    processLine(line)
}

キーポイント:

  • FileHandle.bytes戻るAsyncSequence<UInt8>
  • .lines拡張機能はバイトストリームを文字列ストリームに変換し、行ごとに分割します
  • 標準入力、パイプライン データ、その他のストリーミング シナリオの処理に適しています

URLからファイルを読み取ります

08:16

let url = URL(fileURLWithPath: "/tmp/somefile.txt")
for try await line in url.lines {
    print(line)
}

キーポイント:

  • URL.linesSwift 5.5で追加された新しい便利な属性です
  • ファイルのオープン、クローズ、エラーを自動的に処理します
  • ファイル全体を一度にメモリにロードせずに、1 行ずつ読み取ります。

URLSession からバイト ストリームを取得する

08:49

let (bytes, response) = try await URLSession.shared.bytes(from: url)

guard let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 else {
    throw MyNetworkingError.invalidServerResponse
}

for try await byte in bytes {
    processByte(byte)
}

キーポイント:

  • bytes(from:)戻る(AsyncBytes, URLResponse)タプル
  • AsyncBytesはいAsyncSequence<UInt8>
  • バイト単位の処理。ストリーミング解析や大きなファイルのダウンロードに適しています。

特定の通知を待ちます

09:12

let center = NotificationCenter.default
let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first {
    $0.userInfo[NSStoreUUIDKey] == storeUUID
}

キーポイント:

  • notifications(named:)戻るAsyncSequence<Notification>
  • 組み合わせるfirst(where:)条件を満たす最初の通知を待つことができます
  • これは、デリゲート コールバックを実装したり、オブザーバーを追加したりするよりもクリーンです

AsyncStream の使用

11:10

class QuakeMonitor {
    var quakeHandler: (Quake) -> Void
    func startMonitoring()
    func stopMonitoring()
}

let quakes = AsyncStream(Quake.self) { continuation in
    let monitor = QuakeMonitor()
    monitor.quakeHandler = { quake in
        continuation.yield(quake)
    }
    continuation.onTermination = { @Sendable _ in
        monitor.stopMonitoring()
    }
    monitor.startMonitoring()
}

let significantQuakes = quakes.filter { quake in
    quake.magnitude > 3
}

for await quake in significantQuakes {
    displaySignificantEarthquake(quake)
}

キーポイント:

  • AsyncStreamコンストラクターはクロージャーを受け入れます。continuation値を生成するために使用される
  • continuation.yield()シーケンスに値をポストする
  • continuation.onTerminationリソースをクリーンアップするためにシーケンスがキャンセルされるときに呼び出されます
  • AsyncStream一緒にいることができますfiltermap他の演算子と組み合わせて使用​​する

AsyncStream 処理エラー

エラーをスローする必要がある場合は、次を使用しますAsyncThrowingStream

let events = AsyncThrowingStream(Event.self) { continuation in
    let monitor = EventMonitor()
    monitor.eventHandler = { result in
        switch result {
        case .success(let event):
            continuation.yield(event)
        case .failure(let error):
            continuation.finish(throwing: error)
        }
    }
    monitor.start()
}

キーポイント:

  • AsyncThrowingStreamcontinuation.finish(throwing:)エラーを報告するため
  • 反復処理時に必要for try await
  • エラーが発生するとシーケンスが終了します。next()コールリターンnil

重要ポイント

1. URL.lines を使用して大きなファイルを処理する

アプリで大きなテキスト ファイル (ログ、CSV、JSON 行) を処理する必要がある場合は、それらを一度にメモリに読み込まないでください。使用url.lines行ごとに処理され、メモリ使用量は一定です。

実装アイデア: 置き換えるString(contentsOfFile:)のためにfor try await line in url.lines。ループ内で 1 行ずつ解析し、必要な情報を抽出して破棄します。

2. AsyncStream を使用してタイマー コールバックをラップする

伝統的Timer.scheduledTimerこれはターゲット メソッドで処理する必要があるため、ロジックの邪魔になります。使用AsyncStreamラップ後、すべての時間指定イベントをループで処理できます。

実装アイデア: 作成するAsyncStream、存在するTimerコールバックで呼び出されるcontinuation.yield(())。次に、使用しますfor await _ in timerStreamスケジュールされたタスクを処理します。

3. AsyncStream を使用して、NotificationCenter コールバックをラップします。

一部の通知は、特定の条件によってトリガーする必要があります。従来の方法では、最初にオブザーバーを追加してから、コールバックで条件を判断する必要があります。使用notifications(named:)first(where:)条件を満たす通知を直接待つことができます。

実装アイデア: 置き換えるaddObserver(forName:object:queue:using:)のためにawait center.notifications(named:).first { 条件 }。コードがよりクリーンになり、意図がより明確になります。

4. AsyncStream を使用してネットワーク ページングの読み込みをラップする

Web API のページ読み込みでは、多くの場合、次のページへの再帰呼び出しが必要になります。使用AsyncStreamページング シーケンスを線形反復に変えることができます。

実装アイデア: 作成するAsyncStream、非同期クロージャの最初のページをロードします。continuation.yield各データを返し、次のページを再帰的に読み込みます。発信者が行う必要があるのは、for await item in paginatedStream

5. AsyncStream を使用して CoreLocation コールバックをラップする

CLLocationManagerデリゲート コールバックは複数のメソッドに分散されます。使用AsyncStream位置情報の更新は均一に処理できます。

実装アイデア: 必要なイベントごとに作成AsyncStream(のようにdidUpdateLocationsdidFailWithError)、デリゲート メソッドで対応するメソッドを呼び出します。continuation.yield

関連セッション

コメント

GitHub Issues · utterances