Concurrency: Continuation
기존의 콜백 기반 API나 다른 비동기 메커니즘을 async/await 패턴으로 래핑하기 위한 기능completionHandler같이 async/await 패턴이 도입되기 전 비동기 처리에 대한 패턴들을 async/await 패턴으로 사용할 수 있도록 변환한다.asyn
Read original on Velog ↗Original article in Korean
Continuation
- 기존의 콜백 기반 API나 다른 비동기 메커니즘을 async/await 패턴으로 래핑하기 위한 기능
CompletionHandler to Async/Await
- completionHandler같이 async/await 패턴이 도입되기 전 비동기 처리에 대한 패턴들을 async/await 패턴으로 사용할 수 있도록 변환한다.
- async/await 패턴으로 사용되어 에러 처리 로직을 통합되고 코드의 가독성이 향상된다.
// 기존 completionHandler 패턴
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
// 데이터를 성공적으로 받았을 경우의 처리
} else if let error = error {
// 요청 중에 에러가 발생했을 경우의 처리
} else {
// 다른 상황일 경우의 처리
}
}
.resume()
withCheckedContinuation & withUnsafeContinuation
- 두 메서드 모두
continuation을 사용하여 비동기 작업이 완료될 때까지 대기한 후 해당 결과를 반환하거나 오류를 발생시킨다. - 두 메서드는 안정성과 성능에 있어서 차이가 있기 때문에 상황에 따른 적절한 선택이 필요하다.
withCheckedContinuation
- 일반적으로 추천되는 방법으로 비교적 안전한 방식으로 Continuation을 사용한다.
- 내부적으로 Continuation이 올바르게 resume 되었는지 검사하여, 여러 번 resume되면 런타임 오류가 발생한다.
withUnsafeContinuation
- 비교적 안전하지 않은 방식으로 Continuation을 사용하여, 사용에 주의가 필요하다.
- Continuation의 resume 호출에 대한 검사를 수행하지 않아 성능상의 이점이 있을 수 있지만, 잘못된 사용으로 인해 런타임 오류를 유발할 수 있다.
오류를 던지는 처리라면
withCheckedThrowingContinuation혹은withUnsafeThrowingContinuation을 사용한다.
Resume
- Continuation 타입의 메서드로 비동기 작업의 완료를 알려 결과나 오류를 반환한다.
resume(returning:)
- 작업이 성공적으로 완료되었을 때 사용하며, 주어진 값을 반환하여 비동기 작업의 결과로 사용한다.
resume(throwing:)
- 작업 중 오류가 발생했을 때 사용하며, 주어진 오류를 던져서 비동기 작업의 실패를 나타낸다.
Continuation 내 전체 실행에서
continuation은 정확하게 딱 한 번만resume되어야 한다.
class ContinuationNetworkManager {
func getDataAsync(url: URL) async throws -> Data {
// async/await 패턴으로 변환
return try await withCheckedThrowingContinuation { continuation in
// 기존 비동기 처리
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
// 결과 반환
continuation.resume(returning: data)
} else if let error = error {
// 오류 반환
continuation.resume(throwing: error)
} else {
// 오류 반환
continuation.resume(throwing: URLError(.badURL))
}
}
.resume()
}
}
}
class ContinuationViewModel: ObservableObject {
@Published var image: UIImage? = nil
let networkManager = ContinuationNetworkManager()
func getImage() async {
guard let url = URL(string: "https://picsum.photos/200") else { return }
do {
// async/await 패턴으로 사용
let data = try await networkManager.getDataAsync(url: url)
if let image = UIImage(data: data) {
await MainActor.run(body: {
self.image = image
})
}
} catch {
print(error)
}
}
}