관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Async, Await 사용 방법 본문

swift 5 문법

[iOS - swift] Async, Await 사용 방법

jake-kim 2022. 5. 1. 15:19

Async, Await 이란?

  • 기존에 비동기 처리 방식은 DispatchQueue나 completionHandler를 사용하여 처리했지만, 더욱 편하게 비동기 처리할 수 있는 문법
// DispatchQueue 사용한 비동기 처리
DispatchQueue.global.async {

}

// completionHandler를 사용한 비동기 처리
let task = URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in

}).resume()
  • 비동기 처리란 위와 같이 오래걸리는 작업이 있을 때, 그 작업이 끝나는 것을 기다리지 않고 수행하도록 일을 처리하는 것
  • 이 밖에도 대표적인 비동기 처리 방법에는 RxSwift와 Combine가 존재

Async, Await 등장 배경 - call back 지옥

  • 기존 completionBlock 문법은 장황한 코드를 발생
func processImageData1(completionBlock: (_ result: Image) -> Void) {
    loadWebResource("dataprofile.txt") { dataResource in
        loadWebResource("imagedata.dat") { imageResource in
            decodeImage(dataResource, imageResource) { imageTmp in
                dewarpAndCleanupImage(imageTmp) { imageResult in
                    completionBlock(imageResult)
                }
            }
        }
    }
}

processImageData1 { image in
    display(image)
}
  • 에러 핸들링도 복잡
func processImageData2c(completionBlock: (Result<Image, Error>) -> Void) {
    loadWebResource("dataprofile.txt") { dataResourceResult in
        switch dataResourceResult {
        case .success(let dataResource):
            loadWebResource("imagedata.dat") { imageResourceResult in
                switch imageResourceResult {
                case .success(let imageResource):
                    decodeImage(dataResource, imageResource) { imageTmpResult in
                        switch imageTmpResult {
                        case .success(let imageTmp):
                            dewarpAndCleanupImage(imageTmp) { imageResult in
                                completionBlock(imageResult)
                            }
                        case .failure(let error):
                            completionBlock(.failure(error))
                        }
                    }
                case .failure(let error):
                    completionBlock(.failure(error))
                }
            }
        case .failure(let error):
            completionBlock(.failure(error))
        }
    }
}

processImageData2c { result in
    switch result {
    case .success(let image):
        display(image)
    case .failure(let error):
        display("No image today", error)
    }
}
  • async와 await를 통해 코드를 복잡하지 않게 사용이 가능
    • call back 지옥에서 벗어난 코드
func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image

func processImageData() async throws -> Image {
  let dataResource  = try await loadWebResource("dataprofile.txt")
  let imageResource = try await loadWebResource("imagedata.dat")
  let imageTmp      = try await decodeImage(dataResource, imageResource)
  let imageResult   = try await dewarpAndCleanupImage(imageTmp)
  return imageResult
}

Async, Await를 처리하는 내부 원리 (스레드 관리)

  • sync에서의 스레드 관리
    • 호출 - A함수에서 B함수(sync)를 호출하면, A함수가 실행되던 스레드의 제어권을 B에게 전달
    • 진행 - B함수가 끝날때까지 해당 스레드는 점유되어서 다른 일을 수행하지 않게되는 원리
    • 종료 - B함수가 종료되면 A함수에게 다시 스레드 제어권을 반납

  • async에서의 스레드 관리
    • 호출 - A함수에서 B함수(async)를 호출하면, A함수가 실행되던 스레드의 제어권을 B에게 전달 
    • 진행 - B함수는 async이기 때문에 스레드의 제어권을 포기하는 suspend가 가능 (suspend되면 호출한 A함수도 같이 suspend됨)
    • suspend - 스레드에 대한 제어권은 system으로 가고 시스템은 스레드를 사용하여 다른 작업을 수행
    • resume - 일시 중단된 비동기 함수 B를 다시 실행하는 단계
    • 종료 - B함수가 종료되면 A함수에게 스레드 제어권을 반납

Async, Await 사용 방법

예제 코드) 특정 url을 가지고 이미지 url를 획득 -> 이미지 url을 가지고 Data(contentsOf:)로 이미지 데이터 획득 -> UIImageView에 표출

  • async, await를 사용하지 않은 경우
    • URLSession에서 request
    • response로 받아온 값을 가지고 디코딩하여 completion에 이미지 url을 넘겨줌
    • 사용하는 쪽에서 이미지 url을 가지고 data 획득
    • data를 가지고 이미지 뷰에 표출
@objc private func didTapButton() {
  guard let url = URL(string: "https://reqres.in/api/users?page=2") else { return }
  self.requestImageURL(requestURL: url) { [weak self] urlString in
    DispatchQueue.global().async {
      guard
        let url = URL(string: urlString),
        let data = try? Data(contentsOf: url)
      else { return }
      DispatchQueue.main.async {
        self?.imageView.image = UIImage(data: data)
      }
    }
  }
}

func requestImageURL(requestURL: URL, completion: @escaping (String) -> Void) {
  URLSession.shared.dataTask(
    with: requestURL,
    completionHandler: { data, response, _ in
      guard
        let data = data,
        let httpResponse = response as? HTTPURLResponse,
        200..<300 ~= httpResponse.statusCode
      else { return }
      let decoder = JSONDecoder()
      if let decoded = try? decoder.decode(MyModel.self, from: data) {
        completion(decoded.data.first?.avatar ?? "")
      }
    }).resume()
}
  • async, await를 사용할 경우
    • 메소드 시그니처에 async를 붙여서 비동기 작업임을 알리는 키워드를 추가
    • 메소드 내부 구현에서 비동기가 예상되는 곳에 await 키워드를 추가
    • 해당 메소드를 호출하는 쪽에서는 async block에서 호출하고, await키워드를 붙여서 호출
@objc private func didTapButton() {
  guard let url = URL(string: "https://reqres.in/api/users?page=2") else { return }

  async {
    guard
      let imageURL = try? await self.requestImageURL(requestURL: url),
      let url = URL(string: imageURL),
      let data = try? Data(contentsOf: url)
    else { return }
    print(Thread.isMainThread) // true
    self.imageView.image = UIImage(data: data)
  }
}

func requestImageURL(requestURL: URL) async throws -> String {
  print(Thread.isMainThread) // false
  let (data, _) = try await URLSession.shared.data(from: requestURL)
  return try JSONDecoder().decode(MyModel.self, from: data).data.first?.avatar ?? ""
}

* async 블록으로 감싸서 호출하는 경우, 아래처럼 deprecated 되었으므로 Task를 사용

Task { // <- async대신 Task 사용
  guard
    let imageURL = try? await self.requestImageURL(requestURL: url),
    let url = URL(string: imageURL),
    let data = try? Data(contentsOf: url)
  else { return }
  print(Thread.isMainThread) // true
  self.imageView.image = UIImage(data: data)
}

* 전체 코드:  https://github.com/JK0369/ExAsyncAwait

 

* 참고

https://developer.apple.com/videos/play/wwdc2021/10095/

https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md

Comments