Swift Concurrency框架之Async Squences/Streams

文章系列:

Async Squences/Streams

在介紹Concurrency Framework中的Async Squences/Streams焕参,我們先回顧一下swift的集合中的 Sequence和Iterators丈氓。

Swift集合中的Sqeuence

swift集合中的Sequence是一系列相同類型值的集合就珠,并提供了對(duì)這些值的迭代能力。

for element in someSequence {
    doSomething(with: element)
}

Sequence 協(xié)議的定義:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    func makeIterator() -> Iterator
}

Sequence 協(xié)議需要實(shí)現(xiàn)makeIterator方法符欠,并返回一個(gè)Iterator闯估,Iterator遵循IteratorProtocol:

public protocol IteratorProtocol {
    associatedtype Element
    public mutating func next() -> Self.Element?
}

IteratorProtocol需要實(shí)現(xiàn)next方法,返回存儲(chǔ)的值對(duì)象。當(dāng)沒有下一個(gè)元素返回nil昔头。
我們以下載一系列的url的任務(wù)為例子,使用同步Squence方式:

struct RemoteDataSequence: Sequence {
    var urls: [URL]

    func makeIterator() -> RemoteDataIterator {
        RemoteDataIterator(urls: urls)
    }
}

為了返回?cái)?shù)據(jù)影兽,我們需要實(shí)現(xiàn)RemoteDataIterator類型揭斧,設(shè)計(jì)上我們使用index跟蹤下一個(gè)待下載的urls數(shù)組索引

struct RemoteDataIterator: IteratorProtocol {
    var urls: [URL]
    fileprivate var index = 0

    mutating func next() -> Data? {
        guard index < urls.count else {
            return nil
        }

        let url = urls[index]
        index += 1

        // If a download fails, we simply move on to
        // the next URL in this case:
        guard let data = try? Data(contentsOf: url) else {
            return next()
        }

        return data
    }
}

我們現(xiàn)在可以通過for循環(huán)來(lái)遍歷訪問下載的所有圖片數(shù)據(jù)

for data in RemoteDataSequence(urls: urls) {
    ...
}

雖然我們通過Sqeuence實(shí)現(xiàn)了一個(gè)簡(jiǎn)潔的批量下載器,但是批量下載使用同步的方式顯然比較難于接受峻堰,這樣會(huì)完全阻塞線程讹开。接下來(lái)我們通過使用asynchronous sequence來(lái)達(dá)到我們的要求。

Asynchronous iterations

Swift 5.5中Concurrency為了方便并行任務(wù)的開發(fā)捐名,提供了AsyncSequence旦万,使用方式類似同步版本的Sequence。針對(duì)批量下載器我們可以這樣改造一下:

struct RemoteDataSequence: AsyncSequence {
    typealias Element = Data

    var urls: [URL]

    func makeAsyncIterator() -> RemoteDataIterator {
        RemoteDataIterator(urls: urls)
    }
}

AsyncSequence重要實(shí)現(xiàn)其實(shí)是在RemoteDataIterator中镶蹋,Concurrency為RemoteDataIterator的next返回方法添加了async成艘。通過URLSession的async-wait API,我們可以異步下載我們的數(shù)據(jù):

struct RemoteDataIterator: AsyncIteratorProtocol {
    var urls: [URL]
    fileprivate var urlSession = URLSession.shared
    fileprivate var index = 0

    mutating func next() async throws -> Data? {
        guard index < urls.count else {
            return nil
        }

        let url = urls[index]
        index += 1

        let (data, _) = try await urlSession.data(from: url)
        return data
    }
}

通過AsyncSequence的改造贺归,現(xiàn)在我們的批量下載器已經(jīng)可以全異步執(zhí)行淆两,不過在我們?cè)L問數(shù)據(jù)時(shí)還是需要調(diào)用awaittry,數(shù)據(jù)會(huì)通過后臺(tái)線程下載并允許我們使用for循環(huán)來(lái)遍歷訪問

for try await data in RemoteDataSequence(urls: urls) {
    ...
}

在for循環(huán)中拂酣,如果一個(gè)步驟拋出了異常則循環(huán)會(huì)中止秋冰,這樣有利于簡(jiǎn)化異常捕獲的處理。如果不想要異常導(dǎo)致循環(huán)中斷踱葛,也可以實(shí)現(xiàn)無(wú)異常的方法丹莲。

Asynchronous streams

通過實(shí)現(xiàn)AsyncIteratorProtocol有時(shí)候還是稍嫌麻煩(需要自定義 AsyncIteratorProtocol 的類型),Concurrency提供了AsyncStreamAsyncThrowingStream尸诽。在AsyncStreamAsyncThrowingStream構(gòu)造閉包函數(shù)中甥材,需要使用Task來(lái)執(zhí)行異步任務(wù),使用yield方法來(lái)返回?cái)?shù)據(jù)性含,同時(shí)調(diào)用finish來(lái)告知是否存在異常洲赵。上面的例子可以改造為:

func remoteDataStream(
    forURLs urls: [URL],
    urlSession: URLSession = .shared
) -> AsyncThrowingStream<Data, Error> {
    AsyncThrowingStream { continuation in
        Task {
            do {
                for url in urls {
                    let (data, _) = try await urlSession.data(from: url)
                    continuation.yield(data)
                }

                continuation.finish(throwing: nil)
            } catch {
                continuation.finish(throwing: error)
            }
        }
    }
}

現(xiàn)在我們可以同樣使用for來(lái)遍歷我們的下載數(shù)據(jù):

for try await data in remoteDataStream(forURLs: urls) {
    ...
}

AsyncStreamAsyncThrowingStream可以認(rèn)為是AsyncSequence協(xié)議的具體實(shí)現(xiàn),相當(dāng)于ArraySequence的具體實(shí)現(xiàn)商蕴。在開發(fā)中使用stream可以簡(jiǎn)化我們的異步程序編寫叠萍。
在Apple的響應(yīng)式框架Combine也提供了對(duì)AsyncSequence的兼容,可以輕松地將任何publisher都轉(zhuǎn)換為AsyncSequence的值對(duì)象绪商。上面的下載器可以使用Combine來(lái)改寫:

func remoteDataPublisher(
    forURLs urls: [URL],
    urlSession: URLSession = .shared
) -> AnyPublisher<Data, URLError> {
    urls.publisher
        .setFailureType(to: URLError.self)
        .flatMap(maxPublishers: .max(1)) {
            urlSession.dataTaskPublisher(for: $0)
        }
        .map(\.data)
        .eraseToAnyPublisher()
}

AnyPublisher轉(zhuǎn)換為AsyncSequence苛谷,我們只需要訪問publisher的values屬性:

let publisher = remoteDataPublisher(forURLs: urls)

for try await data in publisher.values {
    ...
}

文章參考: async sequences streams and combine

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市格郁,隨后出現(xiàn)的幾起案子腹殿,更是在濱河造成了極大的恐慌独悴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锣尉,死亡現(xiàn)場(chǎng)離奇詭異刻炒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)自沧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門坟奥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人拇厢,你說我怎么就攤上這事爱谁。” “怎么了旺嬉?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵管行,是天一觀的道長(zhǎng)厨埋。 經(jīng)常有香客問我邪媳,道長(zhǎng),這世上最難降的妖魔是什么荡陷? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任雨效,我火速辦了婚禮,結(jié)果婚禮上废赞,老公的妹妹穿的比我還像新娘徽龟。我一直安慰自己,他們只是感情好唉地,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布据悔。 她就那樣靜靜地躺著,像睡著了一般耘沼。 火紅的嫁衣襯著肌膚如雪极颓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天群嗤,我揣著相機(jī)與錄音菠隆,去河邊找鬼。 笑死狂秘,一個(gè)胖子當(dāng)著我的面吹牛骇径,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播者春,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼破衔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钱烟?” 一聲冷哼從身側(cè)響起晰筛,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤校仑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后传惠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迄沫,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年卦方,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了羊瘩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盼砍,死狀恐怖尘吗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浇坐,我是刑警寧澤睬捶,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站近刘,受9級(jí)特大地震影響擒贸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜觉渴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一介劫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧案淋,春花似錦座韵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瓣距,卻和暖如春黔帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旨涝。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工蹬屹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人白华。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓慨默,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親弧腥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子厦取,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容