How to use Continuations in Swift (withCheckedThrowingContinuation) | Swift Concurrency #7
continuation
을 리턴async await
함수로 핸들링하기continuation.resume
을 통한 데이터 리턴 및 에러 던지기Continuation
리턴하기Continuation
리턴 함수 구현MainActor
바인딩 func fetchData2(url: URL) async throws -> Data {
// Convert API's completion handler to async version
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))
}
// A closure that takes an UnsafeContinuation parameter. You must resume the continuation exactly once.
}
.resume()
}
}
import SwiftUI
protocol CheckedContinuationBootCampProtocol {
func fetchData(url: URL) async throws -> Data
func fetchData2(url: URL) async throws -> Data
func fetchDataFromDatabase() async throws -> UIImage
}
class CheckedContinuationBootCampDataService: CheckedContinuationBootCampProtocol {
func fetchData(url: URL) async throws -> Data {
do {
let (data, _) = try await URLSession.shared.data(from: url)
return data
} catch {
print(error.localizedDescription)
throw error
}
}
func fetchData2(url: URL) async throws -> Data {
// Convert API's completion handler to async version
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))
}
// A closure that takes an UnsafeContinuation parameter. You must resume the continuation exactly once.
}
.resume()
}
}
private func fetchDataFromDatabase(completionHandler: @escaping (_ image: UIImage) -> ()) {
// Mocking Data Fetching from DB
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
completionHandler(UIImage(systemName: "flame.fill")!)
}
}
func fetchDataFromDatabase() async throws -> UIImage {
return await withCheckedContinuation { continuation in
fetchDataFromDatabase { image in
continuation.resume(returning: image)
}
}
}
}
continuation
: 기존 컴플리션 핸들러의 성공/실패에 따라 continuation.resume
리턴 값이 변경 → 이스케이핑 클로저를 사용하는 기존 함수를 async
구문을 따르는 새로운 continuation
으로 연결 가능 → 데이터 패치 코드에서 해당 continuation
을 통해 async await
로 패치 가능class CheckedContinuationBootCampViewModel: ObservableObject {
@Published var image: UIImage? = nil
let dataService: CheckedContinuationBootCampProtocol
let urlString: String
let url: URL
init(dataService: CheckedContinuationBootCampProtocol, urlString: String) {
self.dataService = dataService
self.urlString = urlString
if let url = URL(string: urlString) {
self.url = url
} else {
self.url = URL(string: "https://picsum.photos/1000")!
}
}
func fetchImage() async {
do {
let data = try await dataService.fetchData(url: url)
if let image = UIImage(data: data) {
await MainActor.run(body: {
self.image = image
})
}
} catch {
print(error.localizedDescription)
}
}
func fetchImage2() async {
do {
let data = try await dataService.fetchData2(url: url)
if let image = UIImage(data: data) {
await MainActor.run(body: {
self.image = image
})
}
} catch {
print(error.localizedDescription)
}
}
func fetchImageFromDatabase() async {
do {
let image = try await dataService.fetchDataFromDatabase()
await MainActor.run(body: {
self.image = image
})
} catch {
print(error.localizedDescription)
}
}
}
MainActor
로 실행하기struct CheckedContinuationsBootCamp: View {
@StateObject private var viewModel: CheckedContinuationBootCampViewModel
init(dataService: CheckedContinuationBootCampProtocol, urlString: String) {
_viewModel = StateObject(wrappedValue: CheckedContinuationBootCampViewModel(dataService: dataService, urlString: urlString))
}
var body: some View {
ZStack {
if let image = viewModel.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
}
}
.task {
await viewModel.fetchImage2()
}
}
}