티스토리 뷰

반응형




AsyncSequence protocol에 대해서 알아보자

오늘은 AsyncSequence protocol을 알아보겠습니다. AsyncSequence protocol은 기존 Iteration이 가능하게 해주는 Sequence 프로토콜에 Async 성격이 추가된 프로토콜입니다.

AsyncSequence를 준수하는 애들은 for try await in loop, for await in loop 등을 사용할 수 있게 되고, iOS 13.0이상에서 사용이 가능합니다.

 

선언은 이렇게 되어있습니다. rethrows는 일반적으로 throws closure를 인자로 갖고 있는 메서드에서 사용되는데, @rethrows는 그거랑 관련된 rethrow가 가능한것을 의미하는 annotation일것 같아요. (혹시 이거 명확하게 아시는분 댓글 좀 부탁드립니다. ㅎㅎ)

 


AsyncSequene 공식문서 개요 읽어보기

앞서 말했던 것처럼, AsyncSequence는 Sequence protocol type이랑 유사해요. 단지 거기에 비동기성이 적용된거라고 볼 수 있습니다. 똑같이 next() 메서드로 그 다음 연산결과를 정의할 수 있고, makeIterator() 메서드를 의무적으로 구현해야하는 protocol입니다.

또한 기존 Sequence를 채택한 애들이 for in loop으로 사용되었다면, 이건 for await 혹은 for try await 으로 시작한다는 특징이 있어요. 각각의 순차적으로 값을 기다리고 받는데, 실제로는 모두 async 하게 동작을 합니다.
* await, try await는 반환 부 앞에 async, async throws가 붙는 메서드를 호출할때도 사용되는 예약어입니다.

 

AsyncSequence에는 Element라는 associatedtype이 구현되어있어서 typealias로 Element타입을 명시해서 원하는 타입에 맞게 사용이 가능합니다.

AsyncSequence는 AsyncIteratorProtocol의 iteration 간 다음 반환될 값을 정의하는 next(), AsyncIterator를 정의하는 makeAsyncIterator() 두개를 정의해주어야 AsyncSequence를 준수할 수 있고 비로소 사용이 가능합니다. 이때 정의되는 next() 메서드는 async 혹은 async throws 메서드여야 합니다.

표준 라이브러리에 있는 기본 Sequence 프로토콜에서 제공되는 다양한 operator도 사용이 가능합니다. 예를들위 위 코드와 같이 Sequence에서도 사용했던 contains(_:)메서드를 사용하는 모습을 볼 수 있어요.

 

contains 뿐만 아니라 map 연산자도 사용이 가능한 모습이에요. 차이점이라면 AsyncSequence는 AsyncSequence(혹은 map 클로져 내에 throws method가 사용될 경우 AsyncThrowingMapSequence)를 반환한다는 겁니다.

예시로, 위 map 연산자는 각각의 Int 타입 Element -> String타입의 Element를 가진 AsyncSequence 타입으로 반환해주고 있습니다.

위와 같이 AsyncSequence에서 map연산자를 통해 반환 된 AsyncSequence(혹은 AsyncThrowingMapSequence)는 현재 sequence의 다음 멤버를 애타게 기다리는 상태는 아니에요.

그래서 위 코드를 보시면 map 연산자 결과를 반환받은 stream을 받아두었다가 필요할때 for await in loop 문에 사용할 수 있는 것을 볼 수 있습니다.

 


이제 AsyncSequence를 실제로 사용해보자, 사용방법

이제 개발자문서는 얼추 읽어봤어요. 실제로 사용해보면서 어떤놈이지 알아봅시다. AsyncSequence 구현부에요.

1) AsyncIterator : AsyncIteratorProtocol을 채택한 AsyncIterator가 보입니다. 또한 makeAsyncIterator()를 반드시 구현해야해요.
2) makeAsyncIterator : 근데 makeAsyncIterator()를 보세요. AsyncIterator를 반환합니다. 즉 AsyncIterator도 반드시 사용되는데, AsyncIteratorProtocol을 준수하는 놈이에요. 그렇기에 AsyncIteratorProtocol 준수를 위해 next() 메서드도 구현해주어야 합니다.
3) Element : Element도 associatedtype으로 정의되어있네요. 상황에 따라 채택하는 객체 부에서 typealias로 Element 타입을 정의해서 사용할 수 있어요

 

throw해볼 임의의 커스텀 Error를 하나 생성했구요. Count 구조체를 하나 생성했습니다. AsyncSequence, AsyncIteratorProtocol을 채택하고 관련 메서드를 정의한 모습이에요.

20 ~ 24행) next는 iteration을 진행할때 다음 값을 반환하는 방식이 정의되어있습니다. next() 메서드가 종료될 때마다 index를 1씩 카운팅하고 있구요.(defer)

index가 7이상이 되어버리면 AsyncSequenceError.invalidNumber 에러를 throw하고 있습니다. 6이하인 경우는 반환하고 있어요.

이렇게 error를 throw할 수 있는 async 메서드이기에 반환부 앞에 async throws로 정의되어있습니다.

 

 


for try await - in loop with AsyncSequence

실제로 AsyncSequence를 채택한 counter를 사용해봅시다. 31행에서 생성했구요. Task 블럭내에서 async 동작을 실행하는 모습입니다.

counter는 AsyncSequence프로토콜을 채택한 놈이기 때문에 for try await in loop를 사용할 수 있습니다. try가 사용되었기 때문에 do - catch 블럭이 함께 감사여서 사용되고, error가 throwing 될 경우, catch 블럭을 타게 됩니다.

출력결과는 어떻게 될까요? 위에 next가 어떻게 정의했는지를 생각해보면 정답을 알 수 있어요.

 

6까지 값이 출력되었다가 7이상이 되니 error를 throw 하는 모습입니다. AsyncSequence를 iterate 할때 반드시 try를 사용해야하는 건 아닙니다. 이어서 보겠습니다.

 


for await - in loop with AsyncSequence

아까는 Counter의 구현부 중 next() 메서드 앞에 async throws 붙였는데, 지금은 throws를 뺐어요. 그리고 throw 하는 로직도 제거했습니다.

이렇게 하면 counter를 순회할때 for await - in loop를 통해 값들을 처리할 수 있게 됩니다. try를 하지 않으므로, do - catch 블럭도 필요가 없게 되었구요.
이렇게 되면 출력은 어떻게 될까요? value가 10이상이면 반복문을 빠져나가니, 결과는 아래와 같습니다.

 

 


오늘은 WWDC 2021에서 소개된 async/await 기능과 함께 사용될 수 있는 AsyncSequence의 개념을 개발자 문서로 알아보고, 실제 사용도 해볼 수 있었습니다.

AsyncSequence, for try await in loop 개념은 AsyncThrowingStream, AsyncStream, withThrowingTaskGroup, withTaskGroup와 함께 사용되면 더욱 효율적으로, 더욱 직관성있는 비동기 작업을 할 수 있게 해줍니다.

관련하여 피드백이나 질문 언제든 댓글로 주시면 감사하겠습니다. 그럼 즐거운 코딩 되세요!!

 

반응형
댓글
반응형
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함