아카이브로 돌아가기

Concurrency · concurrency

Concurrency: Continuation

기존의 콜백 기반 API나 다른 비동기 메커니즘을 async/await 패턴으로 래핑하기 위한 기능completionHandler같이 async/await 패턴이 도입되기 전 비동기 처리에 대한 패턴들을 async/await 패턴으로 사용할 수 있도록 변환한다.asyn

Velog 원문 보기

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)
        }
    }
}
아카이브로 돌아가기