🍏/Swift

[Swift] AsyncStream

eungding 2024. 8. 24. 23:47
728x90
반응형

[1] AsyncSequence

 

- iterator 를 통해 elements 에 접근하는 Sequence 와 동일한 개념. 대신 asynchronous 하게 element 를 접근한다는 점이 Sequence 와 다름. 

 

영어가 더 깔끔한 듯.. ?

‘AsyncSequence is a protocol which resembles Sequence and allows you to iterate over a sequence of values asynchronously.’

 

- iterator 의 next() 를 호출하거나 for await loops 를 사용해서 elements 에 접근할 수 있음 

 

- AsyncSequence 프로토콜의 requirement 를 간략히 적어보자면 다음과 같음 

protocol AsyncSequence {
  ...
  associatedtype AsyncIterator : AsyncIteratorProtocol
  func makeAsyncIterator() -> Self.AsyncIterator
}

protocol AsyncIteratorProtocol {
  ...
  func next() async throws -> Self.Element?
}

 

 

- My Own AsyncSequence 를 만든 예시 (출처: Modern Concurrency in Swift)

struct Typewriter: AsyncSequence {
    
    typealias Element = String
    
    let phrase: String
    
    func makeAsyncIterator() -> TypewriterIterator {
        return TypewriterIterator(phrase)
    }
}

struct TypewriterIterator: AsyncIteratorProtocol {
    typealias Element = String
    
    let phrase: String
    var index: String.Index
    
    init(_ phrase: String) {
        self.phrase = phrase
        self.index = phrase.startIndex
    }
    
    // next() returns nil to signify the end of the sequence.
    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])
    }
}


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
 Hello, world!
 */

 

 

[2] AsyncStream

AsyncStream 은 AsyncSequence 를 더 쉽고 빠르게 만들기 위한 방법. (iterator 별도로 안만들어도 됨)

AsyncStream 은 AsyncSequence 를 채택하고 있음.

 

이것도 영어가 더 깔끔한 듯.. 

 

'AsyncStream conforms to AsyncSequence, providing a convenient way to create an asynchronous sequence

without manually implementing an asynchronous iterator

 

 

이 이니셜라이저를 통해 Continuation-Based Stream 을 만들 수 있음. 

 

 

let counter = AsyncStream<String> { continuation in
  
}

 

 

[3] AsyncStream.Continuation 

 

- CheckedContinuation 과 다른 타입임! 

 

- yield (Producing Elements)  /  finish (Finishing the Stream) 메소드를 제공함. 

 

✅ Task.yield 랑 헷갈리면 안됨!! 

Task.yield현재 Task의 실행을 자발적으로 일시 중단하고, 다른 대기 중인 작업들에게 실행 기회를 주는 함수. 

하지만 AsyncStream.Continuation.yield 는 일시 중단이 아니라 값을 방출하는 함수임. 

 

 

 - My Own Timer 만드는 예제 (출처: Modern Concurrency in Swift)

let counter = AsyncStream<String> { continuation in
    var countdown = 3
    
    Timer.scheduledTimer(
        withTimeInterval: 1.0,
        repeats: true
    ) { timer in
        guard countdown > 0 else {
            timer.invalidate() // stop the timer
            continuation.yield("🎉 complete") // yield a final value
            continuation.finish() // complete the sequence
            
            return
        }
        
        continuation.yield("\(countdown)..")
        countdown -= 1
    }
}

for await countdownMessages in counter {
    print(countdownMessages)
}

/*
 3..
 2..
 1..
 🎉 complete
 */

 

 

참고로 이 두 줄을 아래 한줄로 바꿔도 무방. 

[AS IS]
continuation.yield("🎉 complete") // yield a final value
continuation.finish() // complete the sequence
     
     
[TO BE]
continuation.yield(with: .success("🎉 complete")) // shortcut. yield a final value + complete the sequence

 

 

[4] 기존 기능에 AsyncStream 통합하기 

- notification center 을 async stream 을 사용하여 확장한 예제. notifications 를 async sequeuce 를 형태로 받을 수 있도록.

 (출처: Modern Concurrency in Swift)

extension NotificationCenter {

    func notifications(for name: Notification.Name) -> AsyncStream<Notification> {
        AsyncStream<Notification> { continuation in
            NotificationCenter.default.addObserver(
                forName: name,
                object: nil,
                queue: nil
            ) { notification in
                continuation.yield(notification)
            }
        }
    }
}

for await _ in NotificationCenter.default.notifications(for: UIApplication.willResignActiveNotification) {
   // do something
}

for await _ in NotificationCenter.default.notifications(for: UIApplication.didBecomeActiveNotification) {
   // do something
}

 

 

 

이미 애플에서 Receiving Notifications as an Asynchronous Sequence 제공해서

별도 구현없이 이렇게 쓸 수 있음!  (예제로 보여주려고 책 저자분께서 첨부한 것. 혼동 X)

for await _ in NotificationCenter.default.notifications(named: UIApplication.didBecomeActiveNotification) {
    // do something
}

 

 

 

 

반응형