๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ 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)
}

์‚ฌ์‹ค ์ด ๋‹ค์šด๋กœ๋“œ ๋ถ€๋ถ„์€ ๋”ฐ๋กœ ์•ˆ ๋นผ๋„ ๋˜์—ˆ์„ ๊ฒƒ ๊ฐ™๋‹ค ๊ทธ์ „์—๋„...

 

๋ณ€๊ฒฝ ๋!