Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Meet AsyncSequence #25

Closed
samsung-ga opened this issue Jul 12, 2022 · 0 comments
Closed

Meet AsyncSequence #25

samsung-ga opened this issue Jul 12, 2022 · 0 comments
Assignees

Comments

@samsung-ga
Copy link
Collaborator

samsung-ga commented Jul 12, 2022

Meet AsyncSequence

#3 을 보고 오시면 좋습니다.

What is AsyncSequence?

정의와 특징은 아래와 같습니다.

  • AsyncSequence는 두가지 특징을을 제외한다면 Sequence와 동일합니다.
  • 한가지 특징은 반복 중 예외를 던질 수 있습니다. 두번째는 비동기적으로 동작합니다.
  • Sequence가 끝나거나 오류가 발생할 수 있습니다.
  • for문 break, continue 사용이 가능합니다.

기존에는 클로저를 이용해서 비동기코드를 반복적으로 돌렸다면 이제는 for-await-in문을 이용하여 익숙한 구믄으로 반복할 수 있습니다.

// ✅ 일반 AsyncSequence 예제
for await quake in quakes {
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}
// ✅ break 사용 예제
for await quake in quakes {
    if quake.location == nil {
        break
    }
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

// ✅ continue 사용 예제
for await quake in quakes {
    if quake.depth > 5 {
        continue
    }
    if quake.magnitude > 3 {
        displaySignificantEarthquake(quake)
    }
}

반복 도중 예외를 던질 수 있습니다. do catch문 안에서 try를 통해 가능합니다.

do {
    for try await quake in quakeDownload {
        ...
    }
} catch {
    ...
}

그리고 두가지 iteration을 동시에 돌릴 수도 있습니다. 이는 Task를 통해 감쌀 수 있고, 이후 cancel 함수를 호출하여 취소할 수 있습니다.

let iteration1 = Task {
    for await quake in quakes {
        ...
    }
}

let iteration2 = Task {
    do {
        for try await quake in quakeDownload {
            ...
        }
    } catch {
        ...
    }
}

//... later on  
iteration1.cancel()
iteration2.cancel()

Usage and APIs

macOS Monterey, iOS 15, tvOS 15 및 watchOS 8에서 사용 가능한 여러가지 AsyncSequence API가 존재하지만 그 중 주요 내용 일부만 보여드리겠습니다.

  • 파일에서 각 byte를 읽을 때, 비동기로 읽을 수 있습니다.
for try await line in FileHandle.standardInput.bytes.lines {
    ...
}
  • URL에서 byte나 한 줄씩 데이터를 받아올 때도 비동기로 처리할 수 있습니다.
let url = URL(fileURLWithPath: "/tmp/somefile.txt")
for try await line in url.lines {
    ...
}
  • URLSession에서 바이트를 읽을 때 비동기로 읽어올 수 있습니다.
  • URLSession에는 더욱 다양하게 비동기로 읽는 메소드가 새롭게 추가되었기 때문에 Use async/await in URLSession 세션을 확인하면 좋습니다.
let (bytes, response) = try await URLSession.shared.bytes(from: url)

guard let httpResponse = response as? HTTPURLResponse,
      httpResponse.statusCode == 200 /* OK */
else {
    throw MyNetworkingError.invalidServerResponse
}

for try await byte in bytes {
    ...
}
  • Notification 이벤트를 기다려서 받아올 때도 비동기로 받을 수 있습니다.
let center = NotificationCenter.default
let notification = await center.notifications(named: .NSPersistentStoreRemoteChange).first {
    $0.userInfo[NSStoreUUIDKey] == storeUUID
}

이외에도 Sequence에서 사용할 수 있는 메소드와 동일한 기능들이 비동기적으로 작동할 수 있게 추가되었습니다.

스크린샷 2022-07-14 오후 4 33 13

Adopting AsyncSequence

이제는 iteration을 돌리는 것은 알겠지만 나만의 AsyncSequence를 만들 수 있는 지에 대해 궁금할 수 있습니다.
기존 코드에 적용하는 방법에 초점을 맞추어 설명합니다.

AsyncSequnce와 궁합이 잘 맞는 디자인 패턴이 handler와 delegate입니다.
AsyncStream을 구성할 때 타입과 클로저를 추가해야합니다. 클로저에는 contiinuation과 terminaion을 처리해주어야 합니다. 아래와 같이 구성하게 된다면 다른 사용 부분에서 동일한 로직을 반복해서 사용할 필요가 줄어듭니다.

// ✅ QuakeMonitor 클래스에서 비동기작업을 AsyncSequence로 처리하기 위해 아래 세가지 파라미터 추가
class QuakeMonitor {
    var quakeHandler: (Quake) -> Void
    func startMonitoring()
    func stopMonitoring()
}

//  ✅ AsyncStream 추가
let quakes = AsyncStream(Quake.self) { continuation in
    let monitor = QuakeMonitor()
    monitor.quakeHandler = { quake in
        continuation.yield(quake)
    }
    continuation.onTermination = { _ in
        monitor.stopMonitoring()
    }
    monitor.startMonitoring()
}

//  ✅ 사용하기
let significantQuakes = quakes.filter { quake in
    quake.magnitude > 3
}

for await quake in significantQuakes {
    ...
}

AsyncStream은 기존 코드를 AsyncSequence가 되도록 조정하는 좋은 방법입니다. AsyncStream`에서 생성되는 유일한 출처는 구성할 때의 클로저이기 때문입니다.

오류를 던질 수 있도록 하려면 AsyncThrowingStream을 사용해서 만들면 됩니다. AsyncStream과 차이점은 오류 처리를 할 수 있다는 점입니다.

Ref

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants