AsyncSequence 란
기존의 Sequence는 한 번에 하나씩, 단계별(step)로 진행할 수 있는 값 목록(list of values)입니다.
여기에 비동기성을 추가한 것이 바로 AsyncSequence입니다.
내부 구현부
먼저 AsyncSequence 내부를 살펴봅시다.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@rethrows public protocol AsyncSequence {
// AsyncIterator는 반복을 위해 AsyncIteratorProtocol을 구현해줘야 하네요.
associatedtype AsyncIterator : AsyncIteratorProtocol
// 사용할 요소의 타입을 정해줄 수 있는 것 같네요.
associatedtype Element where Self.Element == Self.AsyncIterator.Element
// 반복동작을 위해 위에 만든 AsyncIterator를 리턴합니다.
func makeAsyncIterator() -> Self.AsyncIterator
}
구조는 Async(비동기)라는 키워드만 붙었지 Sequence와 완전히 동일한 것 같네요.
Sequence에 대한 자세한 정보는 [iOS/Swift] 프로토콜 Sequence 을 참고해 주세요.
개요
Sequence로 요소를 반복하는 가장 일반적인 방법은 for문(for-in loop)을 사용하는 것입니다.
대신 비동기 작업이 추가되므로 await 통하여 대기 지점을 만들어 줘야 되겠죠.
for await value in AsyncSequenceType {}
AsyncSequence가 종료되는 조건으로는
next()작업에서 리턴 값으로nil을 반환할 때- 에러가 발생할 때 (throwing an error)
비동기적으로 작업을 한다는 의미는 실패를 할 수 있다는 의미를 갖고 있습니다. (즉. 에러를 던질 수 있죠)
이제 아래에서 예제에서 반복을 만들고 종료하는 법을 같이 확인해 봅시다.
커스텀 타입에서 프로토콜 AsyncSequence 적용 방법
Sequence와 거의 동일한 방법으로 커스텀 타입을 만듭니다.
[iOS/Swift] 프로토콜 Sequence 의 커스텀 타입을 만드는 방법 및 공식문서 를 참고해 주세요.
struct Danny: AsyncSequence {
typealias Element = Int
func makeAsyncIterator() -> DannyAsyncIterator {
return DannyAsyncIterator(count: 5)
}
}
struct DannyAsyncIterator: AsyncIteratorProtocol {
var count: Int
mutating func next() async -> Int? {
if count < 0 { return nil }
defer { count -= 1 }
return count
}
}
let danny = Danny()
// 비동기 작업이므로 Task로 묶어서 사용
Task {
for await value in danny {
print(value)
}
}
// 5
// 4
// 3
// 2
// 1
// 0
AsyncIteratorProtocol(반복자)를 만들 때, next()에서 count가 0이 될 때까지 반복하게 만들어 줬습니다.
(0이 되면 nil을 만나게 되므로 반복이 종료됩니다.)
또한, 비동기 작업이므로 Task로 묶어서 사용해 줬습니다.
AsyncSequence와 AsyncIteratorProtocol 합쳐서 사용하기
사용자 타입을 Sequence로 만들 때는 Element와 makeAsyncIterator()가 생략이 가능했지만,
🤦 AsyncSequence에서는 기본 구현이 제공이 안되므로 꼭 구현을 해줘야 합니다.
struct Danny: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Int
var count: Int
mutating func next() async -> Int? {
if count < 0 { return nil }
defer { count -= 1 }
return count
}
func makeAsyncIterator() -> Danny {
return self
}
}
let danny = Danny(count: 5)
Task {
for await value in danny {
print(value)
}
}
// 5
// 4
// 3
// 2
// 1
// 0
에러처리와 같이 사용하기
일단 에러를 만들기 위해 enum 생성을 해줬습니다.
enum NumberError: Error {
case lessThanZero
var printMessage: Void {
switch self {
case .lessThanZero:
print("Error: 음수를 감지함!")
}
}
}
크게 바뀐 점은 없고, 다만 반복 구현을 하는 next() 메서드를 만들어 줄 때,
에러 구현을 위해 async 뒤에 명시적으로 throws 키워드를 붙여주고, throw를 통해 에러를 받아 줍시다.
마지막으로 실행 시 try, do, catch로 에러 처리만 해 주면 됩니다.
struct Danny: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Int
var count: Int
// throws, throw를 통해 에러를 정의
mutating func next() async throws -> Int? {
if count < 0 { throw NumberError.lessThanZero }
defer { count -= 1 }
return count
}
func makeAsyncIterator() -> Danny {
return self
}
}
let danny = Danny(count: 5)
// try, do, catch로 에러 처리
Task {
do {
for try await value in danny {
print(value)
}
}
catch {
NumberError.lessThanZero.printMessage
}
}
// 5
// 4
// 3
// 2
// 1
// 0
// Error: 음수를 감지함!
count가 0이 되는 순간 throw로 에러를 반환하고 반복이 멈추는 것을 확인할 수 있습니다.
다음에는 AsyncSequence와 비슷하지만, 더욱 쉽게 사용 가능한 AsyncStream에 대하여 알아보겠습니다.
이렇게 오늘은 AsyncSequence에 대하여 알아보았습니다.
부족한 설명이지만, 조금은 이해 가셨나요?
틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃
참고
Apple Developer Documentation
developer.apple.com
Apple Developer Documentation
developer.apple.com
[iOS/Swift] 프로토콜 Sequence
Sequence란? 순차적이고 반복적인 동작을 할 수 있게 만드는 프로토콜입니다. 즉, 순차적인 나열이죠. 선언 protocol Sequence 개요 설명은 공식문서 기반으로 할 예정이므로 자세한 내용을 알고 싶다면
ios-daniel-yang.tistory.com
'Xcode > Swift 문법' 카테고리의 다른 글
| [iOS/Swift] 형 변환 (Type Casting) (0) | 2023.01.31 |
|---|---|
| [iOS/Swift] AsyncStream / AsyncThrowingStream (0) | 2023.01.26 |
| [iOS/Swift] 프로토콜 Sequence (0) | 2023.01.19 |
| [iOS/Swift] AssociatedTypes (프로토콜을 위한 Generic문법) (0) | 2023.01.13 |
| [iOS/Swift] 프로토콜 Equatable (타입 비교) (0) | 2023.01.12 |
