SwiftConcurrency
를 공부하다가AsyncStream
를 사용할만한 일이 생겨 적용하며 정리해보았다. 다음과 같이 인증 화면에서 시간을 카운트다운 하는 기능을AsyncStream
를 사용해 적용해보려고 한다.
AsyncStream
- 비동기 Iterator를 제공하는 프로토콜인 AsyncSequence를 쉽게 구현할 수 있도록 제공되는 인터페이스
AsyncSequence
를 준수하기 때문에map
,filter
,contains
등 익숙한 고차함수 메소드를 사용할 수 있다.
코드
@MainActor
func timerStream() -> AsyncStream<Int> {
return AsyncStream<Int> { continuation in
/// 시간 제한 5분
var validTimeSeconds = 5 * 60 - 1
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true){ timer in
guard validTimeSeconds >= 0 else {
timer.invalidate()
continuation.finish()
return
}
continuation.yield(validTimeSeconds)
validTimeSeconds -= 1
}
}
}
/// 사용
Task {
for await time in timerStream() {
String(format: "%02d:%02d", time / 60, time % 60)
}
}
/* 출력
4:59
4:58
...
0:01
0:00
*/
Async.Continuation
을 받는 클로저를 통해 초기화하여 데이터 스트림을 제공한다.
yield(_:)
메소드를 동해 스트림에 데이터를 전달하고,finish()
메소드를 통해 스트림을 종료한다.
for await in
구문을 통해 사용한다.
AsyncThrowingStream
AsyncStream
에서 에러 핸들링이 추가된 버전.
finish()
와yield(_:)
외에finish(throwing: )
추가되어 에러를 던질수 있다.
코드
@MainActor
func timerStream() -> AsyncThrowingStream<Int, Error> {
return AsyncThrowingStream<Int, Error> { continuation in
var validTimeSeconds = 5 * 60 - 1
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true){ timer in
guard self.validTimeSeconds >= 0 else {
timer.invalidate()
continuation.finish(throwing: TimerError.timeout)
return
}
continuation.yield(validTimeSeconds)
validTimeSeconds -= 1
}
}
}
/// 사용
Task {
do {
for try await seconds in timerStream() {
self.validTimeLabel.text = String(format: "%02d:%02d", seconds / 60, seconds % 60)
}
}
catch {
if error as? TimerError == TimerError.timeout {
/// 타임아웃 알럿 띄움
await self.showExpiredTimeAlert()
}
}
}
/* 출력
4:59
4:58
...
0:01
0:00
❗️타임아웃 알럿
*/
AsyncStream vs Combine
- Concurrency 메소드들은 동시성 프로그램을 대응하기 위해 Swift 언어 자체에 내장된 오퍼레이션.
- Combine은 이벤트처리나 스트림 처리에 중점을 두고 설계를 한 프레임워크.
→ 분명 겹치는 부분이 있기 때문에 서로를 대체해서 사용할 수 있을 것 같다.
→ Combine을 가져다 쓰면 많은 작업을 쉽게 하지만 더 무거워질 것 같다. → SwiftUI에서는 Combine이 훨 편할듯 …?
Reference
Uploaded by N2T