본문 바로가기
iOS/트러블 슈팅

[iOS] Image url 통해 가져와서 view에 띄우거나 갤러리 다운로드하는 방법 2가지 비교

by MINT09 2024. 2. 14.

이번에 unsplash의 api를 받아서 json 데이터를 확인해보니 신기한 점이 있었다. image는 따로 url 종류로 주어지고, 해당 url을 다시 받아와서 사용해야 한다는 것! 때문에 image를 다운로드 하여 imageView에 넣어주는 protocol을 하나 만들었다. 

protocol ImageViewDownloadable {
    var imageView: UIImageView { get set }
}

 

 

그리고 extension으로 이 프로토콜을 채택하고 있다면 image를 보여줄 수도 있고, 갤러리에 다운받을 수도 있는 메서드들을 각각 구현하였다. 

1. URLSession.shared.dataTask , @escaping closure

그렇게 만든 첫 방법은 dataTask의 @escaping closure를 사용하는 것이다. 

func downloadImageUseDataTaskCallBack(url: URL) {
    let dataTask = URLSession.shared.dataTask(with: url) { data, response, error in
       if let error = error as NSError? {
            print(error.localizedDescription)
            self.imageView.image = UIImage(named: "noImage")
            return
        }
            
        guard let data,
              let image = UIImage(data: data) else {
            self.imageView.image = UIImage(named: "noImage")
            print("download error: \(String(describing: error?.localizedDescription))")
            return
        }
        Task { @MainActor in
            self.imageView.image = image
        }
    }
    dataTask.resume()
}
func downloadImageToGalleryUseDataTaskCallBack(url: URL) {
    let dataTask = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error as NSError? {
            print(error.localizedDescription)
            return
        }
            
        guard let data,
              let image = UIImage(data: data) else {
            print("download error: \(String(describing: error?.localizedDescription))")
            return
        }
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
    }
    dataTask.resume()
}

다만 이는 무조건 protocol에 imageView를 가지고 있어야 했고 같은 내용이 반복되어야 했다. 

가독성이 좋지도 않았고, 기본적으로 dispatchQueue를 사용하고 있기에 Swift concurrency보다 스레드를 과하게 사용하게 되니 성능상 좋지 않을 것 같았다. 거기다가 imageView의 image에 할당해주기 위해 @MainActor를 사용하고 있는데 View는 View의 단에서 변경해주는 것이 맞을 것 같아서 변경하였다. 

2. Async await 사용

protocol ImageViewDownloadable { }

extension ImageViewDownloadable {
    func downloadImage(url: URL) async throws -> UIImage {
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              (200...299).contains(httpResponse.statusCode) else {
            throw NetworkError.invalidURL
        }
        
        guard let image = UIImage(data: data) else {
            throw NetworkError.noData
        }
        
        return image
    }
}

때문에 async awiat을 사용하여 UIImage를 반환하는 형식으로 바꾸었다. 

이렇게 하면 프로퍼티로 imageView를 가지고 있을 필요도 없어서 만일 private 처리가 가능하다면 해줄 수 있다. 

func configureImage(title: String, url: URL) {
    Task {
        do {
            let image = try await downloadImage(url: url)
            imageView.image = image
        } catch {
            imageView.image = UIImage(named: "noImage")
        }
    }
}

또한 사용하는 곳에서 이렇게 에러를 만나면 "noImage"의 이미지로 바꾸도록 해줄 수 있고, 다운로드하는 부분은 더 간단해진다.

@objc private func tappedDownloadButton() {
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}

사실 이 다운로드 부분은 따로 안 빼도 되었을 것 같다 그전에도...

 

변경 끝!