Download images with Async/Await, @escaping, and Combine | Swift 동시성 #2

Sung Daegyu·2024년 2월 28일
post-thumbnail

#2 Download images with Async/Await, @escaping, and Combine

💡url을 통해 이미지를 비동기로 다운하는 방법 3가지를 알아봅시다.

코드 구조는 다음과 같습니다.

  • struct **DownloadImageAsync: View → 뷰를 표현**
  • class DownloadImageAsyncViewModel: ObservableObject
    • loader : DownloadImageAsyncImageLoader
    • fun fetchImage() 3가지 방식으로 loader를 통해 이미지를 가져옵니다.
  • class DownloadImageAsyncImageLoader
    • url: String 다운 받을 url 주소
    • func handleResponse(data: Data?, response: URLResponse?) -> UIImage? 다운 받은 data를 image로 변환해줍니다.
      func handleResponse(data: Data?, response: URLResponse?) -> UIImage? {
          guard
            let data = data,
            let image = UIImage(data: data),
            let response = response as? HTTPURLResponse,
            response.statusCode >= 200 && response.statusCode < 300 else {
            return nil
          }
          return image
        }

1. @escaping을 사용해 이미지 다운하기

func downloadWithEscaping(completionHandler: @escaping (_ image: UIImage?, _ error: Error?) -> Void) {
    URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
      let image = self?.handleResponse(data: data, response: response)
      completionHandler(image, error)
    }
    .resume()
  }
  • @escaping 키워드로 명시한 completionHandler를 통해 후에 클로져로 결과값을 다룰 수 있습니다. -> 하단 코드의 [weak self] image, error
  • completionHandler 클로져가 후에 실행됨으로써 비동기처럼 구현된다고 생각하면 될 것 같네요.
func fetchImage() {
loader.downloadWithEscaping { [weak self] image, error in
	DispatchQueue.main.async {
		self?.image = image
  }
}
  • 강의에서는 다음 두 코드 사이에 await (시간이 걸려서 기다림..)이 있다고 생각하면 이해하기 편하다고 합니다.
  • loader.downloadWithEscaping "await" { [weak self] image, error in

2. Combine을 사용해 이미지 다운로드 하기.

func downloadWithCombine() -> AnyPublisher<UIImage?, Error> {
    URLSession.shared.dataTaskPublisher(for: url)
      .map(handleResponse)
      .mapError({ $0 })
      .eraseToAnyPublisher()
  }
  • .dataTaskPublisher를 통해 combine을 사용할 수 있습니다.
  • .map(handleResponse).mapError를 통해 data → UIImage?URLError → Error 작업을 해줄 수 있습니다.
  • 코드가 전보다 확실히 간결하네요!
func fetchImage() {
	loader
		.downloadWithCombine()
    .receive(on: DispatchQueue.main)
    .sink { _ in
        
    } receiveValue: { [weak self] image in
	      self?.image = image
    }
    .store(in: &cancellables)
}
  • receiveValue를 통해 결과값인 image를 처리할 수 있습니다.

3. async/await를 통해 이미지 다운하기.

func downloadWithAsync() async throws -> UIImage? {
    do {
      let (data, response) = try await URLSession.shared.data(from: url, delegate: nil)
      return handleResponse(data: data, response: response)
    } catch {
      throw error
    }
  }
  • async/await를 통해 비동기를 구현하는 함수는 함수 옆에 async 키워드를 입력해야 해당 함수들을 사용할 수 있습니다.
  • URLSession.shared.data(from: delegate)가 비동기를 지원하는 함수입니다.
  • 앞선 코드들 보다 훨씬 간결해진 것을 볼 수 있습니다.
  • 또한 함수의 결과값/반환값(async throws → UIImage?)이 completionHandler와 달리 명시되어 있어서 훨씬 직관적입니다.
func fetchImage() async {
let image = try? await loader.downloadWithAsync()
  await MainActor.run {
    self.image = image
  }
}
  • fetchImage() 함수도 이제 동시성을 사용하므로 async 키워드를 붙여 줍니다.
  • 앞서 사용했던 dispatchQueue.main을 async에서는 사용 못하기 때문에 대신에 MainActor.run을 사용해 줍니다.

참고

Swiftful Thinking 강의
https://www.youtube.com/watch?v=p6q1RmYUsNU&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=1

profile
대규의 개발로그

0개의 댓글