What is AsyncSequence

์šฉ๋Ÿ‰์ด ์ข€ ํฐ csv ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค๊ณ  ํ•˜์ž. ๋งŒ์•ฝ ํ•ด๋‹น ํŒŒ์ผ์„ ๋ชจ๋‘ ๋ฐ›์€ ๋’ค์— ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๊ณ  ํ•œ๋‹ค๋ฉด, ์˜ค๋žœ ์‹œ๊ฐ„ ๋’ค์—๋‚˜ ๊ฐ€๋Šฅํ•  ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์„œ asyncSequence๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ต‰์žฅํžˆ ๋ฐ˜์‘์„ฑ์žˆ๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

struct QuakesTool {
    static func main() async throws {
        let endpointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!
 
        // header line ์Šคํ‚ตํ•˜๊ณ  ๋ผ์ธ ํ•˜๋‚˜์”ฉ ์ˆœํšŒ
        // ๋Œ๋ฉด์„œ ์ง„๋„, ์‹œ๊ฐ„, ์œ„๊ฒฝ๋„ ์ถ”์ถœ
        for try await event in endpointURL.lines.dropFirst() {
            let values = event.split(separator: ",")
            let time = values[0]
            let latitude = values[1]
            let longitude = values[2]
            let magnitude = values[4]
            print("magnitude: \(magnitude), time: \(time), latitude: \(latitude), longitude: \(longitude)")
        }
    }
}

endpointURL.lines๋Š” URL์— ์žˆ๋Š” property์ด๋‹ค. AsyncLineSequence<URL.AsyncBytes> ํƒ€์ž…์œผ๋กœ ๋˜์–ด ์žˆ์œผ๋ฉฐ, ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋‹ค line์— ํ•ด๋‹นํ•˜๋Š” byte๊ฐ€ ๋ชจ๋‘ ๋‹ค์šด๋กœ๋“œ๋˜๋ฉด ์œ„์˜ loop์•ˆ์˜ ์ฝ”๋“œ๊ฐ€ ๋™์ž‘ํ•œ๋‹ค.

How it works

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

์šฐ๋ฆฌ๊ฐ€ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” for loop์€ ์–ด๋–ป๊ฒŒ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ฐ›์•„๋“ค์ผ๊นŒ?

var iterator = quakes.makeIterator()
while let quake = iterator.next() {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(queke)
    }
}

์ด๋ ‡๊ฒŒ iterator๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ์›์†Œ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋‹ค์Œ ์š”์†Œ๊ฐ€ ์—†์„ ๋•Œ๋Š” nil์„ ๋˜์ณ while ๋ฃจํ”„๋ฅผ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ์ „ํ˜•์ ์ธ Iterator ํŒจํ„ด์ด๋‹ค.

์ดํ„ฐ๋ ˆ์ดํ„ฐ ํŒจํ„ด(iterator pattern): ์ปฌ๋ ‰์…˜ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์„ ๋…ธ์ถœ์‹œํ‚ค์ง€ ์•Š์œผ๋ฉด์„œ๋„ ๊ทธ ์ง‘ํ•ฉ์ฒด ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๋ชจ๋“  ํ•ญ๋ชฉ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ œ๊ณต

๊ทธ๋Ÿผ for await in syntax๋Š” ๋ฌด์—‡์ด ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์ผ๊นŒ?

var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(queke)
    }
}
do {
    for try await quake in quakeDownload {
        if quake.depth > 5 { continue }
        if quake.location == nil { break }
 
        ...
    }
} catch {
 
}

์ด๋ ‡๊ฒŒ! ๋‹ค์Œ next์— ๋Œ€ํ•ด ๋Œ€๊ธฐํ•˜๋Š” ๊ฒƒ์œผ๋กœ๋งŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค. for await in syntax๋Š” ๊ธฐ์กด for loop์—์„œ ์‚ฌ์šฉํ•˜๋˜ continue, break ๋“ฑ์„ ๋™์ผํ•˜๊ฒŒ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค. ๋˜ํ•œ Error handling๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

Encapsulation

์ด์ „ ๊ธ€์—์„œ ์‹ค์ œ๋กœ async ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Task๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Async sequence๋„ Task ์•ˆ์— ์ •์˜ํ•˜์—ฌ ์บก์Šํ™”ํ•˜์—ฌ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

let iteration1 = Task {
    for await quake in quakes {
        if quake.magnitude > 3 {
            displaySignificantEarthquake(queke)
        }
    }
}
 
let iteration2 = Task {
    do {
        for try await quake in quakeDownload {
            if quake.depth > 5 { continue }
            if quake.location == nil { break }
 
            ...
        }
    } catch {
 
    }
}
 
iteration1.cancel()
iteration2.cancel()

Usage and APIs

์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋žตํ•˜๊ฒŒ ์†Œ๊ฐœํ•˜๊ณ  ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜๊ฒ ๋‹ค. ์–ด๋–ค ๊ฒƒ๋“ค์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€๋งŒ ์•Œ์•„๋ณด์ž.

  • FileHandle์„ ํ†ตํ•œ Bytes ์ฝ๊ธฐ๋ฅผ ๋ผ์ธ๋ณ„๋กœ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • URL์œผ๋กœ๋ถ€ํ„ฐ line์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. (๋งจ ์œ„์—์„œ ๋ณธ ์˜ˆ์‹œ: local, remote ์ƒ๊ด€ ์—†์Œ)
  • Notification ์‚ฌ์šฉ..

ํ•ด๋‹น ๋ถ€๋ถ„์€ ์ง€๊ธˆ ์™€๋‹ฟ์ง€ ์•Š์•„, ์ถ”ํ›„์— ์˜์ƒ์„ ๋‹ค์‹œ ๋ณด๋Š” ๊ฒƒ์œผ๋กœ ํ•˜๊ฒ ๋‹ค.

Custom AsyncSequence

๋ฌธ์„œ๋ฅผ ์ฝ๋‹ค๋ณด๋‹ˆ, AsyncSequence๊ฐ€ Protocol์ด๋ผ ์ด๋ฅผ ์ฑ„ํƒํ•˜๋ฉด ๋  ๋“ฏํ•˜์—ฌ ํ•ด๋ณธ๋‹ค.

Sequence

Array, Dictionary, Set ๋“ฑ์€ ๋ชจ๋‘ Sequence์ด๋‹ค. ๋จผ์ €, ์ด Sequence Protocol๋กœ customํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž.

struct CustomSequence: Sequence { // Not working
 
}

๋‹จ์ˆœํ•˜๊ฒŒ Sequence Protocol์„ ์ฑ„ํƒํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์ด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค. ์ด์œ ๋Š” Sequence Protocol์ด ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” method๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

public protocol Sequence {
 
    /// A type representing the sequence's elements.
    associatedtype Element where Self.Element == Self.Iterator.Element
 
    /// A type that provides the sequence's iteration interface and
    /// encapsulates its iteration state.
    associatedtype Iterator : IteratorProtocol
 
    /// Returns an iterator over the elements of this sequence.
    func makeIterator() -> Self.Iterator โœ…
}

Iterator๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” IteratorProtocol ์„ ์ค€์ˆ˜ํ•˜๋Š” ํƒ€์ž…์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

struct CustomIterator: IteratorProtocol {
    typealias Element = Int
 
    private var current: Int = 0
 
    mutating func next() -> Int? {
        self.current += 1
        return self.current
    }
}
 
struct CustomSequence: Sequence {
    func makeIterator() -> some IteratorProtocol {
        return CustomIterator()
    }
}
 
let sequence = CustomSequence()
for i in sequence {
    print(i)
} // ๋ฌดํ•œํžˆ ์ˆซ์ž๊ฐ€ ๋Š˜์–ด๋‚˜๋ฉฐ ์ถœ๋ ฅ

์ด๋Ÿฐ์‹์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ ์ค‘๊ฐ„์— ๊ทธ๋งŒ๋‘๊ณ  ์‹ถ๋‹ค๋ฉด if ๋ฌธ ์•ˆ์—์„œ break์„ ํ•ด์ฃผ๋Š” ๋ฐฉํ–ฅ์ด ์žˆ๊ฒ ๋‹ค. ์‹ค์ œ ๊ตฌํ˜„์ด ์ด๋Ÿฐ์‹์œผ๋กœ ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์•ž์—์„œ ์šฐ๋ฆฌ๊ฐ€ for in loop๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ compiler๊ฐ€ iterator๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค๋งŒ, ์—ฌ๊ธฐ์„œ ํŠน์ • ํƒ€์ž…์— IteratorProtocol์„ ๋™์‹œ์— ์ฑ„ํƒํ•  ๊ฒฝ์šฐ Iterator class๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์ฃผ์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ์ฆ‰, ํƒ€์ž… ์ž์ฒด๊ฐ€ Iterator๋กœ ๋™์ž‘ํ•œ๋‹ค.

struct CustomSequence: Sequence, IteratorProtocol {
    typealias Element = Int
    private var current: Int = 0
 
    func makeIterator() -> Element? {
        self.current += 1
        return current
    }
}

AsyncSequence

๊ธฐ๋ณธ์ ์œผ๋กœ AsyncSequence๋„ Protocol์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

struct CustomSequence: AsyncSequence, AsyncIteratorProtocol {
    typealias Element = Int
 
    private var current: Int = 1
 
    mutating func next() โœ… async โœ… throws -> Int? {
        if self.current == 10 {
            return nil
        }
        self.current += 1
        return current
    }
 
    func makeAsyncIterator() -> CustomSequence { โœ…
        return self
    }
}
 
Task {
    let sequence = CustomSequence()
 
    for try await number in sequence {
        print(number)
    }
}
 
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10

์—ฌ๊ธฐ์„œ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์€ Sequence๊ฐ€ AsyncSequence๋กœ, IteratorProtocol์ด AsyncIteratorProtocol๋กœ ๋ณ€ํ™”ํ–ˆ๋‹ค๋Š” ์ ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  makeAsyncIterator() ๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€๋กœ ๊ตฌํ˜„ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค. Sequence์˜ ๊ฒฝ์šฐ์—๋Š” ์—†์—ˆ์ง€๋งŒ, ์ด๊ฒฝ์šฐ๋Š” ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ , next() ํ•จ์ˆ˜์— throws, async ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค. ๋‚ด๋ถ€ ๊ตฌํ˜„์ด ์ด๋ ‡๊ธฐ ๋•Œ๋ฌธ์—, for (try) await in ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ.

๊ทธ๋Ÿฐ๋ฐ, ์•„๊นŒ for await in์œผ๋กœ๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์—ˆ๋‚˜? throws๊ฐ€ ๋ถ™๊ฒŒ ๋˜๋ฉด for try await in ์œผ๋กœ ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€? ๊ทธ๋ž˜์„œ ์ด throws๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด ์•ž์—์„œ ๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ for await in์œผ๋กœ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค!

์ผ๋‹จ ์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋Œ๋ฆฌ๋ฉด 10๊นŒ์ง€๋ง ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๊ณ  ์ข…๋ฃŒ๋œ๋‹ค. sequence์—์„œ ๊ฒฐ๊ณผ๋ฅผ ์ข…๋ฃŒํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด nil์„ ๋ฆฌํ„ดํ•˜๋ฉด ๋œ๋‹ค.

Summary

  • AsyncSequence๋Š” step๋ณ„๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ’์„ ์ œ๊ณต + ๋น„๋™๊ธฐ์„ฑ ์ถ”๊ฐ€ํ•œ Protocol์ด๋‹ค.
  • ํ•ด๋‹น protocol์„ ์ค€์ˆ˜ํ•œ ์นœ๊ตฌ๋“ค์€ ์œ„์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ for (try) await in syntax๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— await์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋˜ํ•œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์—, ์‹คํŒจํ•  ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ try ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ๋‹ค์Œ ์กฐ๊ฑด(์‹คํŒจํ•˜๊ฑฐ๋‚˜, ๋ชจ๋‘ ์ˆœํšŒํ•˜๊ฑฐ๋‚˜)์ธ ๊ฒฝ์šฐ loop๋Š” ๋๋‚œ๋‹ค.
  • Custom Async Sequence๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ Custom Sequence๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๊ณผ ์ƒ๋‹นํžˆ ์œ ์‚ฌํ•˜๋‹ค.

Reference