Skip to the content.

Swift AsyncSequence

AsyncSequence의 기초에 대해 적어보려고 합니다.

Modern Concurrency in Swift (raywenderlich.com) 내용의 일부 + 사족? 입니다.

AsyncStream

딱 보자마자 느낀건 RxSwift의 Observable, Combine의 Publisher와 굉장히 유사합니다.

let counter = AsyncStream<String> { continuation in
  var countdown = 3
  
  Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    guard countdown > 0 else {
      timer.invalidate()
      continuation.yield(with: .success("🎉 " + message))
      continuation.finish()
      return
    }
    
    continuation.yield("\(countdown) ...")
    countdown -= 1
  }
}

for await countdownMessage in counter {
  print(countdownMessage)
}

이런 식으로 카운트를 짤 수 있어요. Observable/Publisher와 굉장히 유사하죠. 근데 cancel이 나도 Timer가 안 끝나는 문제가 있는데요. 아마 raywenderlich에서 AsyncStream을 보여주려고 일부러 저렇게 한 것 같은데… 아래와 같이 개선이 가능해요.

var countdown = 3

let timerSequence = Timer
  .publish(every: 1, tolerance: 0, on: .main, in: .common, options: nil)
  .autoconnect()
  .values

for await _ in timerSequence {
  guard countdown > 0 else {
    print("🎉 " + message)
    break
  }
  
  print("\(countdown) ...")
  countdown -= 1
}

위 코드는 Combine을 썼는데… RxSwift에도 async/await 지원이 있다던데 될지도?

AsyncSequence와 AsyncIteratorProtocol

struct TypeWriterIterator: AsyncIteratorProtocol {
    typealias Element = String
    
    let phrase: String
    var index: String.Index
    
    init(_ phrase: String) {
        self.phrase = phrase
        self.index = phrase.startIndex
    }
    
    mutating func next() async throws -> String? {
        guard index < phrase.endIndex else {
            return nil
        }
        
        try await Task.sleep(nanoseconds: 1_000_000_000)
        
        defer {
            index = phrase.index(after: index)
        }
        
        return String(phrase[phrase.startIndex...index])
    }
}

struct TypeWriter: AsyncSequence {
    typealias AsyncIterator = TypeWriterIterator
    
    typealias Element = String
    
    let phrase: String
    
    func makeAsyncIterator() -> TypeWriterIterator {
        return TypeWriterIterator(phrase)
    }
}
for try await item in TypeWriter(phrase: "Hello, World") {
    print(item)
}

결과

H
He
Hel
Hell
Hello
Hello,
Hello, 
Hello, W
Hello, Wo
Hello, Wor
Hello, Worl
Hello, World

다만 cancel이 일어나도 Thread.sleep이 안 끝나는 문제가 있는데… 요것도 Combine 쓰면 해결될듯?