[Project 일지] 여행 기록 앱 만들기 (13) - image loading 속도 개선하기

현재 원더보드에 올라와 있는 게시물을 선택하면 이미지를 불러오는 로딩 속도가 상당히 느려서 이용성이 저하되는 문제가 있었다.

 

 

이용자 피드백에서도 해당 내용이 거론되었고 내부적으로도 이 부분은 우선적으로 수정해야하는 의견이 많아 얼른 수정해야 한다.

이 점을 개선하기 위해 Kingfisher를 활용해서 이미지 캐싱을 손보고 로딩 속도를 개선하고자 한다.

 

이미지 로딩 로직 수정

먼저 이전의 저장되어있는 이미지를 불러오는 구현을 살펴보면

func updateSelectedImages(with mediaItems: [Media]) {
    selectedImages.removeAll()
    
    let group = DispatchGroup()
    
    for media in mediaItems {
        guard URL(string: media.url) != nil else { continue }
        group.enter()
        loadImage(from: media.url) { [weak self] image in
            guard let self = self else {
                group.leave()
                return
            }
            if let image = image {
                let location: CLLocationCoordinate2D? = (media.latitude != nil && media.longitude != nil) ? CLLocationCoordinate2D(latitude: media.latitude!, longitude: media.longitude!) : nil
                if !self.selectedImages.contains(where: { $0.0 == image && $0.1 == media.isRepresentative && $0.2?.latitude == location?.latitude && $0.2?.longitude == location?.longitude }) {
                    self.selectedImages.append((image, media.isRepresentative, location))
                }
            }
            group.leave()
        }
    }
    
    group.notify(queue: .main) {
        if let galleryCell = self.detailViewCollectionView.cellForItem(at: IndexPath(item: 0, section: 0)) as? GalleryCollectionViewCell {
            galleryCell.selectedImages = self.selectedImages
        }
    }
}
func loadImage(from urlString: String, completion: @escaping (UIImage?) -> Void) {
    guard let url = URL(string: urlString) else {
        completion(nil)
        return
    }
    
    AF.request(url).response { response in
        if let data = response.data, let image = UIImage(data: data) {
            completion(image)
        } else {
            completion(nil)
        }
    }
}

이미지가 로드 되는 과정을 보면 알라모파이어를 통해 이미지를 가져오게 되는데 원본 그대로 가져오게 되어 리뉴얼로 크게 이미지를 구현하는 것으로 바뀐 지금에는 그 데이터가 너무 커 firebase에서도 용량을 많이 차지하는 경향이 있었다.

 

이 부분을 개선하기 위해 킹피셔를 활용해 이미지 캐싱을 하고 빠르게 이미지 로드 하는 로직으로 변경하였다.

func updateSelectedImages(with mediaItems: [Media]) {
        selectedImages.removeAll()
        
        let group = DispatchGroup()
        
        for media in mediaItems {
            guard let url = URL(string: media.url) else { continue }
            group.enter()
            
            KingfisherManager.shared.retrieveImage(with: url, options: nil) { [weak self] result in
                guard let self = self else {
                    group.leave()
                    return
                }
                
                switch result {
                case .success(let value):
                    let image = value.image
                    let location: CLLocationCoordinate2D? = (media.latitude != nil && media.longitude != nil) ? CLLocationCoordinate2D(latitude: media.latitude!, longitude: media.longitude!) : nil
                    if !self.selectedImages.contains(where: { $0.0 == image && $0.1 == media.isRepresentative && $0.2?.latitude == location?.latitude && $0.2?.longitude == location?.longitude }) {
                        self.selectedImages.append((image, media.isRepresentative, location))
                    }
                case .failure(let error):
                    print("Failed to load image: \\(error)")
                }
                
                group.leave()
            }
        }
        
        group.notify(queue: .main) { [weak self] in
            guard let self = self else { return }
            if let galleryCell = self.detailViewCollectionView.cellForItem(at: IndexPath(item: 0, section: 0)) as? GalleryCollectionViewCell {
                galleryCell.selectedImages = self.selectedImages
            }
        }
    }

retrieveImage

KingfisherManager.shared.retrieveImage(with: url, options: nil)

킹피셔의 싱글톤을 활용해서 이미지 로딩 작업을 관리하도록 한다.

retrieveImage(with:options:completionHandler:)

메서드는 지정된 URL에서 비동기적으로 가져온다.

switch result {
case .success(let value):
    let image = value.image
    ...
case .failure(let error):
    print("Failed to load image: \\(error)")
}

result는 Result<RetrieveImageResult, KingfisherError> 타입인데 이미지 가져오는 걸 성공하면 value.image로 이미지에 접근 할 수 있다.

 

킹피셔의 자동 캐싱을 활용해서 로드도니 이미지를 메모리와 디스크에 자동으로 캐시하고 이 코드는 캐시옵션을 설정하지 않았지만 기본적으로 캐싱을 수행한다.

 

모든 이미지 로딩 작업이 백그라운드에서 비동기적으로 수행되기 떄문에 더욱 빠르게 로딩이 가능해진다.

이를 통해서 이미지가 너무 늦게 뜨는 것을 수정하고 조금 더 앱 이용성을 챙길 수 있었다.