서론.
Swift로 코드를 작성하다보면 completion handler를 사용하는 기본 메서드들이 많이 있습니다.
예를 들면, URLSession의 `dataTask`메서드인데요.
// URLSession을 사용하여 데이터 다운로드
func downloadData(from url: URL, completion: @escaping (Data?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// 네트워크 작업이 완료되면 클로저 호출
DispatchQueue.main.async {
completion(data, error)
}
}
task.resume()
}
// 사용 예시
if let url = URL(string: "https://example.com/api/data") {
downloadData(from: url) { (data, error) in
if let error = error {
print("다운로드 중 에러 발생: \(error.localizedDescription)")
} else if let data = data {
print("다운로드 성공! 받은 데이터: \(data)")
// 데이터 처리 또는 UI 업데이트 등을 수행할 수 있음
}
}
}
위와 같은 방법으로 `completion`으로 넘겨주는 방식으로 사용하게 됩니다.
예시.
이러한 방법을 살펴보면 당장은 문제가 없어보이지만, 컴플리션 핸들러를 사용하는 메서드들을 중첩해서
사용하게되면 코드가 복잡해지고 분기 처리가 어려워 진다는 단점이 있습니다.
func fetchThumbnail(for id: String, completion: @escaping (Result<UIImage, Error>) -> Void) {
let request = thumbnailURLRequest(for: id)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(.failure(FetchError.badID))
} else {
guard let image = UIImage(data: data!) else {
completion(.failure(FetchError.badImage))
return
}
image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
guard let thumbnail = thumbnail else {
completion(.failure(FetchError.badImage))
return
}
completion(.success(thumbnail))
}
}
}
task.resume()
}
출처: https://developer.apple.com/videos/play/wwdc2021/10132/
이런식으로 말이죠.
분기처리가 복잡하게 되어있어 코드의 가독성이 많이 떨어져 보입니다.
그래서 Swift는 async await를 활용해 위와같은 문제를 해결할 수 있는 방법을 제공하는데요.
참고: WWDC - Meet async/await in Swift
이번 포스팅은 어쩔 수 없이 컴플리션 핸들러로 구현되어있는 메서드를 사용해야할 때
async await로 사용할 수 있게 감싸는 방법에 대해서 알아보고자 합니다.
본론.
Swift에서 `completion` 핸들러를 async로 감싸는 방법을 제공하고 있습니다.
`withCheckedContinuation(function:_:)`, `withCheckedThrowingContinuation(function:_:)` 같은
메서드로감싸 async하게 구현할 수 있습니다.
그렇다면
func downloadData(from url: URL, completion: @escaping (Data?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// 네트워크 작업이 완료되면 클로저 호출
DispatchQueue.main.async {
completion(data, error)
}
}
task.resume()
}
위의 예시 메서드를 어떻게 수정할 수 있을까요?
1. `withCheckedContinuation(function:_:)` 사용하기
func newDownload(from url: URL) async -> Data? {
return await withCheckedContinuation { continuation in
downloadData(from: url) { data, error in
continuation.resume(returning: data)
// or
// continuation.resume(with: .success($0))
}
}
}
이런식으로 `withCheckedContinuation(function:_:)` 을 사용해 메서드의 클로저로
컴플리션 핸들러를 사용하는 메서드를 감싸서 구현할 수 있습니다.
단 위와 같이 작성할 경우에는 에러를 반환하지 않을때에는 적합하지만, 에러를 반환해야할 경우에는 다른 메서드를 사용합니다.
2. `withCheckedThrowingContinuation`를 사용해 에러 throw 하기
func newDownload(from url: URL) async throws -> Data? {
return try await withCheckedThrowingContinuation{ continuation in
downloadData(from: url) { data, error in
if let data = data {
continuation.resume(returning: data)
} else {
continuation.resume(throwing: error!)
}
}
}
}
func run() {
Task {
do {
let result = try await newDownload(from: URL(string: "https://example.com/api/data"))
print(result)
} catch {
print(error)
}
}
}
3. `withCheckedContinuation` 를 사용해 Result에 에러를 담아서 return 하기
func newDownload(from url: URL) async -> Result<Data, OpenMarketAPIError> {
return await withCheckedContinuation{ continuation in
downloadData(from: url) { data, error in
if let data = data {
continuation.resume(returning: .success(data))
} else {
continuation.resume(returning: .failure(.responseFail))
}
}
}
}
func run() {
Task {
let result = newDownload(from: URL(string: "https://example.com/api/data")!)
switch result {
case .success(let data):
print(data)
case .failure(let error):
print(error.localizedDescription)
}
}
}
결론.
실제 프로젝트에 적용을 해보았을 때에는 `throw`를 사용해 `do-catch`구문을 통해서 에러핸들링을 하는 방법 보다는
`Result` 타입으로 명확하게 Error를 보내서 Switch문을 통해 에러 타입에 따른 분기처리를 해주는 것이
조금 더 명확하게 코드를 작성할 수 있었던 것 같습니다.
또한 `Alamofire`같은 네트워크 라이브러리를 사용할 때 더 유용하다고 느꼈습니다.
컴플리션 핸들러로 구현되어있는 메서드를 자유롭게 async로 감싸서 사용할 수 있었습니다.
구현 코드 : Github
'Swift' 카테고리의 다른 글
Swift - Optional과 Optional Binding (1) | 2023.12.29 |
---|---|
Swift - 타입 캐스팅 톺아보기 (0) | 2023.12.29 |
Swift - [weak self] 톺아보기 (0) | 2023.12.21 |
Swift - ARC 톺아보기 (0) | 2023.12.14 |
iOS 공부하는 중🌱