Swift 中的 AsyncSequence

AsyncSequence是併發性框架和SE-298提案的一部分。它的名字意味着它是一個提供異步、順序和迭代訪問其元素的類型。換句話說:它是我們在Swift中熟悉的常規序列的一個異步變體。

就像你不會經常創建你的自定義序列一樣,我不期望你經常創建一個自定義的AsyncSequence實現。然而,由於與AsyncThrowingStream和AsyncStream等類型一起使用,你很可能不得不與異步序列一起工作。因此,我將指導你使用AsyncSequence實例進行工作。

什麼是 AsyncSequence?

AsyncSequence是我們在Swift中熟悉的Sequence的一個異步變體。由於它的異步性,我們需要使用await關鍵字,因爲我們要處理的是異步定義的方法。如果你沒有使用過async/await,我鼓勵你閱讀我的文章:Swift 中的 async/await

值可以隨着時間的推移而變得可用,這意味着一個AsyncSequence在你第一次使用它時可能不包含也可能包含一些,或者全部的值。

重要的是要理解AsyncSequence只是一個協議。它定義瞭如何訪問值,但並不產生或包含值。AsyncSequence協議的實現者提供了一個AsyncIterator,並負責開發和潛在地存儲值。

創建一個自定義的 AsyncSequence

爲了更好地理解AsyncSequence是如何工作的,我將演示一個實現實例。然而,在定義你的AsyncSequence的自定義實現時,你可能想用AsyncStream來代替,因爲它的設置更方便。因此,這只是一個代碼例子,以更好地理解AsyncSequence的工作原理。

下面的例子沿用了原始提案中的例子,實現了一個計數器。這些值可以立即使用,所以對異步序列沒有太大的需求。然而,它確實展示了一個異步序列的基本結構:

struct Counter: AsyncSequence {
    typealias Element = Int

    let limit: Int

    struct AsyncIterator : AsyncIteratorProtocol {
        let limit: Int
        var current = 1
        mutating func next() async -> Int? {
            guard !Task.isCancelled else {
                return nil
            }

            guard current <= limit else {
                return nil
            }

            let result = current
            current += 1
            return result
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        return AsyncIterator(howHigh: limit)
    }
}

如您所見,我們定義了一個實現 AsyncSequence 協議的Counter 結構體。該協議要求我們返回一個自定義的 AsyncIterator,我們使用內部類型解決了這個問題。我們可以決定重寫此示例以消除對內部類型的需求:

struct Counter: AsyncSequence, AsyncIteratorProtocol {
    typealias Element = Int

    let limit: Int
    var current = 1

    mutating func next() async -> Int? {
        guard !Task.isCancelled else {
            return nil
        }

        guard current <= limit else {
            return nil
        }

        let result = current
        current += 1
        return result
    }

    func makeAsyncIterator() -> Counter {
        self
    }
}

我們現在可以將self作爲迭代器返回,並保持所有邏輯的集中。

注意,我們必須通過提供typealias來幫助編譯器遵守AsyncSequence協議。

next()方法負責對整體數值進行迭代。我們的例子歸結爲提供儘可能多的計數值,直到我們達到極限。我們通過對Task.isCancelled的檢查來實現取消支持。你可以在這裏閱讀更多關於任務和取消的信息

異步序列的迭代

現在我們知道了什麼是AsyncSequence以及它是如何實現的,現在是時候開始迭代這些值了。

以上述例子爲例,我們可以使用Counter開始迭代:

for await count in Counter(limit: 5) {
    print(count)
}
print("Counter finished")

// Prints:
// 1
// 2
// 3
// 4
// 5
// Counter finished

我們必須使用 await關鍵字,因爲我們可能會異步接收數值。一旦不再有預期的值,我們就退出for循環。異步序列的實現者可以通過在next()方法中返回nil來表示達到極限。在我們的例子中,一旦計數器達到配置的極限,或者迭代取消,我們就會達到這個預期:

mutating func next() async -> Int? {
    guard !Task.isCancelled else {
        return nil
    }

    guard current <= limit else {
        return nil
    }

    let result = current
    current += 1
    return result
}

許多常規的序列操作符也可用於異步序列。其結果是,我們可以以異步的方式執行映射和過濾等操作。

例如,我們可以只對偶數進行過濾:

for await count in Counter(limit: 5).filter({ $0 % 2 == 0 }) {
    print(count)
}
print("Counter finished")

// Prints: 
// 2
// 4
// Counter finished

或者我們可以在迭代之前將計數映射爲一個String

let counterStream = Counter(limit: 5)
    .map { $0 % 2 == 0 ? "Even" : "Odd" }
for await count in counterStream {
    print(count)
}
print("Counter finished")

// Prints:
// Odd
// Even
// Odd
// Even
// Odd
// Counter finished

我們甚至可以使用AsyncSequence而不使用for循環,通過使用contains等方法。

let contains = await Counter(limit: 5).contains(3)
print(contains) // Prints: true

注意,上述方法是異步的,意味着它有可能無休止地等待一個值的存在,直到底層的AsyncSequence完成。

繼續你的Swift併發之旅

如果你喜歡你所讀到的關於異步序列的內容,你可能也會喜歡其他的併發主題:

結論

AsyncSequence是我們在Swift中熟悉的常規Sequence的異步替代品。就像你不會經常自己創建一個自定義Sequence一樣,你也不太可能創建自定義的異步序列。相反,我建議你看一下AsyncStreams

轉自 AsyncSequence explained with Code Examples

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章