Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] 여러개의 이미지 prefetch, 이미지 다운로드 방법 (#SDWebImage, DispatchGroup, RxSwift) 본문

iOS 응용 (swift)

[iOS - swift] 여러개의 이미지 prefetch, 이미지 다운로드 방법 (#SDWebImage, DispatchGroup, RxSwift)

jake-kim 2024. 4. 10. 01:17

이미지 prefetch, 이미지 다운로드

  • SDWebImage를 사용하여 쉽게 prefetch, 이미지 다운로드가 가능
  • prefetch를 해놓으면 이미지를 memory, disk 캐싱해놓는데 큰 이미지나 즉각적으로 화면에 보여져야하는 이미지들은 prefetch해놓는것이 중요 
    • SDWebImage의 캐싱 구현 코드 분석은 이전 포스팅 글 참고

이미지 prefetch

  • 이미지를 관리할 때는 보통 로컬이 아닌 서버로부터 url을 받아서 이 url로 이미지를 띄워주는데, 이 때 url을 가지고 이미지를 바로 여러장을 띄워주려고 할때 시간이 오래 걸리는 문제가 존재
  • prefetch를 사용하면 SDWebImage에서 memory, disk 캐싱하여 효율적으로 이미지 표출이 가능
  • ImageManager라는 이름으로 prefetch 접근할 수 있도록 선언하고 SDWebImagePrefetcher를 통해 손쉽게 prefetch 기능 사용
import SDWebImage

enum ImageManager {
    private static let Prefetcher = SDWebImagePrefetcher.shared
    
    static func prefetch(urls: [URL], completion: @escaping () -> Void) {
        Prefetcher.prefetchURLs(urls, progress: nil) { successedCount, skippedCount in
            completion()
        }
    }
}

이미지 다운로드

  • 이미지 다운 역시도 SDWebImageDownloader.shared로 손쉽게 사용이 가능
static func downloadData(url: URL, completion: @escaping (Data) -> Void) {
    Downloader.downloadImage(with: url, options: [], progress: nil) { (image, data, error, finished) in
        if let data = data, finished {
            completion(data)
        } else if let error = error {
            print("이미지 다운로드 실패 URL \(url): \(error.localizedDescription)")
        }
    }
}

이미지 여러개 다운로드 (DispatchGroup으로 구현 방법)

  • 여러개의 URL을 전달하고 모든 URL의 이미지를 다운 받았을 때 콜백을 받고싶은 경우?
  • DispatchGroup 인스턴스를 하나 만들고 async작업이 다 끝난 후에 notify(queue:) 함수로 모든 async작업이 끝난 타이밍에 completion을 넘겨줌
static func downloadDatas(urls: [URL], completion: @escaping ([Data]) -> Void) {
    let group = DispatchGroup()
    var imagesData = [Data]()
    
    urls.forEach { url in
        // TODO...
    }

    group.notify(queue: .main) {
        print("다운 완료:", imagesData.count)
        completion(imagesData)
    }
}
  • url.forEach를 돌면서 async작업이 시작하기 전에 enter()하여 기다리는 개수를 설정하고 async작업이 끝나는 시점에는 leave()로 기다리는 개수를 제거하여 0이 되면 위에서 작성한 notify가 실행되도록 구현
urls.forEach { url in
    // 1
    print("enter")
    group.enter()
    
    downloadData(url: url, completion: { data in
        imagesData.append(data)
        // 2
        print("leave")
        group.leave()
    })
}
  • 사용하는 쪽)
let urls = [
    "https://cdn.pixabay.com/photo/2016/02/21/06/22/macbook-air-1213180_1280.jpg",
    "https://cdn.pixabay.com/photo/2019/02/04/06/46/apple-3974057_1280.jpg",
    "https://cdn.pixabay.com/photo/2018/07/09/18/17/apple-3526737_1280.jpg",
    "https://images.pexels.com/photos/4246192/pexels-photo-4246192.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
].compactMap(URL.init(string:))

ImageManager.downloadDatas(urls: urls) { datas in
    print("res:", datas)
}

/*
enter
enter
enter
enter

leave
leave
leave
leave
다운 완료: 4
*/

이미지 여러개 다운로드 (RxSwift로 구현방법)

  • RxSwift로하면 조금 더 간략하게 표현이 가능
  • 마찬가지로 ImageManagerRx라는 타입을 만들고 Single을 반환하여 성공/실패 결과로 받도록 단일 Data를 받는 함수 구현
enum ImageManagerRx {
    private static let Downloader = SDWebImageDownloader.shared
    
    static func downloadData(url: URL) -> Single<Data> {
        .create { observer in
            Downloader.downloadImage(with: url) { image, data, error, finished in
                if let error {
                    observer(.failure(error))
                } else if let data {
                    observer(.success(data))
                } else {
                    observer(.failure(NSError()))
                }
            }
            return Disposables.create()
        }
    }
}
  • 여러개의 데이터를 다 받을때까지 기다려야 하므로 위 함수를 zip으로 묶으면 완성
static func downloadDatas(urls: [URL]) -> Single<[Data]> {
    let observables = urls.map { downloadData(url: $0) }
    return Single.zip(observables)
}

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

* 참고

- https://github.com/SDWebImage/SDWebImage

Comments