Sept 26, 2021, TIL (Today I Learned) - Computed Property, Extension, 캐시처리가 안되는 문제 TroubleShooting

Inwoo Hwang·2021년 9월 25일
0

TIL

목록 보기
7/8
post-thumbnail

Trouble: 캐시처리가 안되는 문제

extension OpenMarketListCollectionViewCell {
    
    // MARK: - configure cell
    
    func configure(itemInformation: [OpenMarketItem], index: Int) {
      let thumbnailDownloadLink = itemInformation[index].thumbnails[0]
      self.itemThumbnail.applyDownloadedImage(link: thumbnailDownloadLink )
    }
}

상품목록에 네트워크통신으로 불러온 이미지를 적용하기 위해 위와 같이 applyDownloadedImage() 메서드를 사용하였습니다.

extension UIImageView {
    private var imageLoader: CachedImageLoader {
        return CachedImageLoader(imageDownloader: ImageDownloader(network: Network()))
    }
    
    func applyDownloadedImage(link: String) {
        imageLoader.loadImageWithCache(with: link) { image in
            DispatchQueue.main.async {
                self.image = image
            }
        }
    }
}

해당 메서드는 UIImageView 를 확장한 뒤 네트워크 작업을 하기 위해 imageLoader 라는 CachedImageLoder 타입 모듈을 연산프로퍼티로 선언한 뒤 loadImageWithCache() 메서드를 통해 이미지를 불러오도록 구현이 되어있습니다.

final class CachedImageLoader {
    private let cache = ImageCache<NSString, UIImage>()
    private let imageDownloader: ImageDownloadable
    
    
    init(imageDownloader: ImageDownloadable) {
        self.imageDownloader = imageDownloader
    }
    
    func loadImageWithCache(with link: String, completion: @escaping (UIImage) ->()) {
        let nsText = link as NSString
        
        if let cachedImage = cache[nsText] {
            return completion(cachedImage)
        }

        imageDownloader.downloadImage(url: link.createURL()) { result in
            switch result {
            case .failure(let error):
                NSLog(error.localizedDescription)
            case .success(let image):
                self.cache.insert(image, forkey: nsText)
                return completion(image)
            }
        }
    }
}

CachedImageLoader 타입은 위와 같이 구현되어 있습니다.

내부에는 cacheimageDownloader 프로퍼티를 갖고 있고 imageDownloader 를 통해 받아온 초기 이미지 데이터를 클래스 내부 cache에 저장하며 UIImage를 반환하게 되고 추후에 동일한 이미지를 받아오게 되면 cache 내부에 해당 이미지가 있는지 확인작업을 한 뒤 만약 캐시에 해당 이미지가 존재한다면 받아온 이미지를 적용하는 로직을 적용하였습니다.

문제가 없어보이는 구조이지만 왠일인지 프로젝트를 실행해도 이미지가 캐시처리가 안되는 것을 확인 하였습니다. 뭐가 문제일까요...

TroubleShooting: 연산프로퍼티의 프로퍼티의 초기화 문제

이렇게 막히는 부분이 있으면 당연히 디버깅을 먼저 해봐야겠죠?

열심히 디버깅을 하다 이상한 부분을 발견하였습니다.

CachedImageLoader 객체 내에서 self.cache.insert(image, forKey: nsText)

해당 부분에 breakpoint를 찍은 뒤 imageCache를 살펴보니 다음과 같은 log를 확인할 수 있었습니다.

<ImageCache<NSString, UIImage>: 0x60000330d580>
<ImageCache<NSString, UIImage>: 0x60000330d940>
<ImageCache<NSString, UIImage>: 0x60000330da80>

두둥! 맙소사. 매번 insert를 할 때마다 ImageCache의 주소가 달라지네요.

왜 그런가 곰곰히 생각 해 보니...UIImageView의 확장에서 연산 프로퍼티를 통해서 imageLoader를 불러올 때

private var imageLoader: CachedImageLoader {
        return CachedImageLoader(imageDownloader: ImageDownloader(network: Network()))
    }

이런식으로 매번 새로운 imageLoader 인스턴스를 생성한 뒤 해당 imageLoader의 cache에다가 image를 저장하고 있던거였습니다. (저는 바보입니다..)

그렇기 때문에 각 cell에서 self.itemThumbnail.applyDownloadedImage(link: thumbnailDownloadLink ) 를 할 때마다 각 각 다른 주소를 갖는 imageLoader 인스턴스의 imageCache에 저장이 되고 있던거죠 ㅡ,ㅡ

문제 해결

UIImageView 의 extension 에서 ImageLoader를 활용하여 Cache를 저장할 수 있고 ViewController의 imageLoader를 활용해서 이미지 캐시를 저장할 수 있는 공용 객체가 필요했고 저는 싱글톤 패턴이 이 때 활용하기에 가장 알맞은 것 같아서 Cache를 공용으로 저장할 수 있는 CacheManager 클래스를 만들었습니다.

import UIKit

final class CacheManager {
    let cache = ImageCache<NSString, UIImage>(totalCostLimit: 500 * 1024 * 1024)
    
    static let shared = CacheManager()
    private init() { }
}

그리고 CachedImageLoader의 내부를 다음과 같이 수정하였어요

func loadImageWithCache(with link: String, completion: @escaping (UIImage) ->()) {
        let nsText = link as NSString
        
        if let cachedImage = CacheManager.shared.cache[nsText] {
            return completion(cachedImage)
        }

        imageDownloader.downloadImage(url: link.createURL()) { result in
            switch result {
            case .failure(let error):
                NSLog(error.localizedDescription)
            case .success(let image):
                CacheManager.shared.cache.insert(image, forkey: nsText)
                return completion(image)
            }
        }
    }

이렇게 구현하니 정상적으로 cache에 이미지가 저장되는 것을 확인할 수 있었습니다.

마치며

extension과 연산프로퍼티에 대해 오늘 하나 더 알아간 것 같아서 뿌듯합니다! 앞으로 오늘과 같은 실수는 더 이상 하지 않을거에요!!

profile
james, the enthusiastic developer

0개의 댓글