티스토리 뷰

Swift/Concurrency

AsyncSequence

Zedd0202 2022. 2. 6. 01:47
반응형

 

안녕하세요 :) Zedd입니다.

오늘은 AsyncSequence에 대해서 공부해보겠습니다. 

 

# Sequence

AsyncSequence가 Sequence와 유사하기 때문에..

Swift ) Sequence도 한번 보고 오시면 좋을 것 같습니다 👀

 

# AsyncSequence

AsyncSequence는 Sequence와 유사하지만,

✔️ 한번에 하나씩 단계(step)별로 진행할 수 있는 값 목록을 제공 + 비동기성을 추가한 타입 ✔️ 입니다. 

AsyncSequence역시 for-in loop에 사용할 수 있는데요.

for value in AsyncSequence타입 {}

자 생각해봅시다. AsyncSequence는 뭔가 비동기~~친구인데, 

사용할 때 값이 전부 or 일부가 아직 없는 상태일 수도 있겠죠!!!!!! 

그래서 AsyncSequence는 

for await value in AsyncSequence타입 {}

await과 같이 사용하게 됩니다. 

그러니까 AsyncSequence는 for-in loop가 아닌 for-await-in loop를 사용하는 것이죠 :D

또한, AsyncSequence가 throwable하다면, for-try-await-in loop를 사용해야합니다. 

왜냐면, 일반 Sequence와 다르게 비동기적으로 요소를 가져오는데,

비동기적으로 가져온다는 것 == 실패할 가능성이 있다는 걸 의미합니다.

그래서 try와 같이 사용되는것이구요 ㅎㅎ

 

💡 참고로 실패하게 되면 for-loop문은 종료됩니다.

그러니까 AsyncSequence가 종료되려면

1. 완전히 끝나거나

2. error가 나거나

인거죠! 요건 아래에서 예제로 더 자세히 보도록 할게요 ㅎㅎ 

 

# 예제

대표적인 AsyncSequence를 살펴보겠습니다. 

let endpointURL = URL(string: "https://zeddios.tistory.com")!
do {
    for try await line in endpointURL.lines {
        print(line)
    }
} catch {

}
/*
<!doctype html>
<html lang="ko">
<head>
....
....
...
</html>
*/

iOS 15에서, URL타입의 인스턴스 프로퍼티로 lines가 나왔습니다.

해당 URL의 리소스 데이터 (= text line)의 비동기 시퀀스입니다. 즉, AsyncSequence이죠. 

(사실은 AsyncLineSequence인데, 일단 궁극적으로 AsyncSequence임) 

이 lines는 throwable 하기 때문에 for-try-await-in loop를 사용한 것을 볼 수 있습니다. 

 

또, RxSwift가 6.5.0버전에서 Swift Concurrency를 지원하게 되었는데요. 

do {
    for try await value in observable.values {
        print("Got a value:", value)
    }
} catch {
    print("Got an error:", error)
}

요런것이 가능해졌죠!!! 

🤓 : observable.values가 AsyncSequence구나..! 

 

# AsyncSequence Operator

Sequence와 유사하다고 계속 말씀드렸죠!

Sequence에서 사용되는 다양한 Operator를 AsyncSequence에서도 동일하게 사용할 수 있습니다. 

let endpointURL = URL(string: "https://zeddios.tistory.com")!
let lineCount = endpointURL.lines.map { $0.count } ✅
do {
    for try await line in lineCount {
        print(line)
    }
} catch {

}
/*
15
16
6
...
*/

이런식으로 사용할 수 있게 되는거죠!

 

# Conforming to the AsyncSequence Protocol

요부분은 Swift ) Sequence를 읽고오시면 이해가 더 잘되실 듯 합니다.

Custom Sequence를 만들때와 거의 똑같은데,

✔️ AsyncSequence, AsyncIteratorProtocol을 준수해야한다는 것

✔️ next()makeAsyncIterator()를 둘 다 구현해야한다는 것. (Sequence때는 next()만 구현하면 됐었음..)

만 다릅니다.

struct Zedd: AsyncSequence, AsyncIteratorProtocol {
    
    typealias Element = Int
    var current = 1

    mutating func next() async throws -> Element? {
        defer { current += 1 }
        return current
    }

    func makeAsyncIterator() -> Zedd {
        self
    }
}

이런식으로 해주면

let zedd = Zedd()

Task {
    do {
        for try await value in zedd {
            if value > 10 { break }
            print(value)
        }
    } catch {

    }
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10

요렇게 zedd를 AsyncSequence로 사용할 수 있습니다.

현재 Zedd내의 next()는

mutating func next() async ✅ throws ✅ -> Element? {
        defer { current += 1 }
        return current
}

throwable한 메소드인데요.

 

[Throw Error]

위에서 실패(Error)가 나면 for-in loop는 종료된다고 그랬죠?

에러를 진짜 내보겠습니다. 

enum ZeddError: Error {
    case someError
}

struct Zedd: AsyncSequence, AsyncIteratorProtocol {

    typealias Element = Int
    var current = 1

    mutating func next() async throws -> Element? {
        if current == 5 { throw ZeddError.someError } ✅
        defer { current += 1 }
        return current
    }

    func makeAsyncIterator() -> Zedd {
        self
    }
}

current가 5가 되면 에러를 던지게 해놨습니다. 

결과는~~

let zedd = Zedd()

Task {
    do {
        for try await value in zedd {
            if value > 10 { break }
            print(value)
        }
    } catch {
        print(error)
    }
}
// 1
// 2
// 3
// 4
// someError ✅

에러가 방출되고 for-loop는 종료된 것을 볼 수 있습니다.

 

[throws 제거] 

mutating func next() async ✅ throws ✅ -> Element? {
        defer { current += 1 }
        return current
}

이 throws를 뺄 수 있습니다. (컴파일 에러 안남) 

그러면!!! 

Task {
    for await value in zedd {
        if value > 10 { break }
        print(value)
    }
}

이렇게 do-catch문도, try문도 없는 for-await-in loop를 사용할 수 있게됩니다. 

 

📝 AsyncSequence를 위처럼 만들수도 있지만, AsyncStream을 사용해서 AsyncSequence를 만들 수도 있습니다 👻

그건 다음 글에서! 

➡️ AsyncStream / AsyncThrowingStream 읽으러 가기 


참고 :

WWDC21 ) Meet AsyncSequence

AsyncSequence

반응형