Swift Concurrency 2편 - Async Method 와 AsyncSequence

김재형·2024년 6월 1일
0
post-custom-banner

들어가기 앞서...

해당 글은 Swift Concurrency 1편을 보고 읽어 주시면 좋습니다!

Sequence

asyncSequence 를 배우기 전에 Sequence 프로토콜이 무엇인지 부터
알아보며 시작해 보도록 하겠습니다.

Sequence 를 알려주겠다고 하면서 왜 저는 Array, Set, Dictionalry 에 관한
사진을 보여 주고 있을까요 ?
그럼 Array, Set, Dictionalry 는 혹시 어떤 프로토콜 타입을 채택하고 있는지 아신가요?
맞습니다! Collection 프토토콜을 채택하고 있습니다.

그렇다면 Collection 프토토콜은 어떠한 프로토콜을 채택 하고 계신지 아시나요?
네. 그 프토토콜이 Sequence 프로토콜 입니다.!

직역: "요소가 비파괴적으로 여러 번 탐색되고 인덱싱된 아래 첨자로 액세스할 수 있는 시퀀스입니다."
와 역시 문서는 너무 친절한것 같습니다! 자 문서가 하고 싶은 말을 다시 생각해 볼꼐요
순차적이고 반복적인 동작을 할수 있게 만들어주는 프로토콜 이라고 정의 해보겠습니다.

그리고, 시퀀스는 무한하거나 유한하며, 여러 번 반복을 보장 안함 의 특징을 가지고 있습니다.
이게 무슨 말이냐면, 1..100 또는 1... 같이 무한 하며, 첫번째 반복은 모두를 순회 하지만, 2번째 부턴 처음부터 반복할지를 보장하지 않는다. 라고 이해 하시면 될것 같습니다!
좀더 쉽게 생각해서 컬렉션 타입에게 인터페이스를 제공 한다 라고 생각 하셔도 됩니다.

예시 코드

// Swfit Sequence protocol 정의
protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    func makeIterator() -> Iterator
    
 // Sequence 프로토콜을 채택하려면 makeIterator() 를 구현 하여야 합니다!
 struct Countdown: Sequence {
    let start: Int

    func makeIterator() -> CountdownIterator {
        return CountdownIterator(start: start)
    }
}

struct CountdownIterator: IteratorProtocol {
    let start: Int
    var current: Int

    init(start: Int) {
        self.start = start
        self.current = start
    }

    mutating func next() -> Int? {
        if current >= 0 {
            let result = current
            current -= 1
            return result
        } else {
            return nil
        }
    }
}

// 사용 예시
let countdown = Countdown(start: 3)
for number in countdown {
    print(number)
}

AsyncSequence

자 간단하게 나마 Sequence 에대해서 알아보았으니
이제 본론인 AsyncSequence 에 대해서 배워보도록 하겠습니다!

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@rethrows public protocol AsyncSequence {

    // AsyncIterator는 next() 반복을 위해서 AsyncIteratorProtocol 을 구현해야합니다.
    associatedtype AsyncIterator : AsyncIteratorProtocol

	// 타입을 정합니다.
    associatedtype Element where Self.Element == Self.AsyncIterator.Element

    // 반복동작을 위해 AsyncIterator는를 반환합니다.
    func makeAsyncIterator() -> Self.AsyncIterator
}


@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@rethrows public protocol AsyncIteratorProtocol {

    associatedtype Element

    mutating func next() async throws -> Self.Element?
}

다시 말하면, 기존 iteration이 해주던 Sequence 프로토콜에 Async 가 추가된 프로토콜 입니다.
또 다시말해, AsyncSequence 를 for - in loop 에 사용할수 있는데요

for value in AsyncSequence타입 {}

하지만 위의 코드처럼 작성하게 되면 데이터가 존재 할지 안할지 불명확 해집니다.
이유는 데이터를 불러오는 중이 였다면 해당값은 아직 없겠죠?

for await value in AsyncSequence타입 {}

그럼 await 키워드를 붙여 기다리게 하면 되겠네요! ( 1편 참고 )

WWDC Example

@main
struct QuakesTool {
   static func main() async throws {
       let endpointURL = URL(string: "")!

       for try await event in endpointURL.lines.dropFirst() {  // 이부분 주목!
           let value = event.split(separator: ",")
           let time = values[0]
           let latitude = values[1]
           let longtitude = values[2]
           let magnitude = values[4]

           print("Magnitude \\(magnitude) on \\(time) at \\(latitude) \\(longtitude)")
       }
   }
}

WWDC 예제 코드를 보면서 좀더 확장해 보죠.
코드 중에 "이부분 주목!" 부분이 보이시나요?
기존의 Sequence 와의 차이점은 Async 인가 아닌가의 차이 인데요.
비동기로 동작 하여, event에 순차적으로 값을 넣어주는 코드 입니다.

예시코드 Feat. WWDC

// 기존 for in 문
for quake in quakes {           
   if quake.magnitude > 3 {
       displaySignificantEarthquake(quake)
   }
}
// Async 적용한 for in 문 
var iterator = quakes.makeAsyncIterator()
while let quake = iterator.next() {
   if quake.magnitude > 3 {
       displaySignificantEarthquake(quake)
   }
}

다시 생각 해보죠
기존의 for in 문에선 Sequence 프로토콜을 채택한 것을 활용하여, next() 를 통해 반복을 수행합니다.
AsyncSequence 도 결국엔 Sequence 프로토콜을 채택한 형태임으로, 비동기 적으로 for - in 문을
사용할수 있습니다. 하지만 빈값이 존재할수도 있기에 아래와 같이 코드가 수정 됩니다.

// `Sequence` 예시
var iterator = quakes.makeIterator()
while let quake = iterator.next() {
   if quake.magnitude > 3 {
       displaySignificantEarthquake(quake)
   }
}

// `AsyncSequence` 예시
var iterator = quakes.makeAsyncIterator()
while let quake = await iterator.next() {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

AsyncStream

AsyncStream은 AsyncSequence를 준수합니다.
비동기 Interator를 직접 구현하지 않더라도 비동기 시퀀스를 작성할수 있습니다.
순서가 있으며, 비동기적으로 생성된 Sequence 이며, 클로저 내부에서 할 일을 정의할 수 있습니다.

Apple 공식 문서 참고 바랍니다.
https://developer.apple.com/documentation/swift/asyncstream

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public struct AsyncStream<Element> {

	public struct Iterator : AsyncIteratorProtocol {
    
        public mutating func next() async -> Element?
    }

	public func makeAsyncIterator() -> AsyncStream<Element>.Iterator
    
    public typealias AsyncIterator = AsyncStream<Element>.Iterator
    
    
     public init(_ elementType: Element.Type = Element.self,
     			bufferingPolicy limit: AsyncStream<Element>.Continuation.BufferingPolicy = .unbounded,
                _ build: (AsyncStream<Element>.Continuation) -> Void)
    
}

extension AsyncStream.Continuation {
    @discardableResult
    public func yield(with result: Result<Element, Never>) -> AsyncStream<Element>.Continuation.YieldResult
    
    @discardableResult
    public func yield() -> AsyncStream<Element>.Continuation.YieldResult where Element == ()
    
}

AsyncStreamcontinuation 에서는 yield를 통해 데이터를 스트림에 제공하거나,
데이터를 받지 못한 경우에는 finish 를 호출하게 됩니다.
AsyncStream AsyncSequence 를 채택한 struct 인걸 확인 할수 있죠.

  • elementType
    • AsyncStream이 생성 하는 Type을 정의해야 합니다.
  • bufferingPolicy
    • 동시적으로 생성된 element들을 모아둘 버퍼의 크기를 정합니다.
  • build
    • 수확(yield)을 어떻게 할지 정의합니다. continuation 인스턴스를 받는데,
      continuation을 통해 스트림에 주입하고 종료할수 있습니다.

AsyncThrowingStream

 public init(
 	_ elementType: Element.Type = Element.self,
 	bufferingPolicy limit: AsyncThrowingStream<Element,
 	Failure>.Continuation.BufferingPolicy = .unbounded,
 	_ build: (AsyncThrowingStream<Element, Failure>.Continuation) -> Void) 
    where Failure == any Error

AsyncStream 은 에러를 던지지 않았으나, AsyncThrowingStream 은 에러를 던집니다.

마무리 하며...

2편의 내용이 엄청 길어 졌습니다..
상당히 생각을 많이 하게 하는 방대한 내용이여서 분량을 이정도로 조절해 보았는데요!
사실 4편으로 생각했는데 6편 으로 증편 될것 같습니다..
이번 Sequence 와 AsyncSequence를 학습하면서 몰랐던 동작 구조에 대해서 학습 할수
있었던 시간이 였던것 같습니다. 이해가 덜 가는 부분은 점차 다음편으로 이어지면서
단단히 다져 보려고 합니다. 다음편에서는 Async / Await를 활용해 보는 편으로 찾아 뵙겠습니다!
감사합니다.

profile
IOS 개발자 새싹이
post-custom-banner

0개의 댓글