Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
SwiftUI/SwiftUI(Intermediate)

22. NSCache 캐시/ 이미지 캐싱

by Toughie 2023. 6. 15.

⭐️NSCache 캐시⭐️

캐시? 많이 들어봤을 것이다. 인터넷 옵션에서 캐시삭제, 쿠키 삭제와 같이!

캐시는 뭘까??

캐시(Cache)는 데이터나 작업의 임시 복사본을 저장해서 이후에 빠른 엑세스를 가능하게 해주는 녀석이다.

인터넷 창을 띄웠을 때 처음에는 여러 이미지들이 로딩되는 시간이 필요해서 버벅이며 보이지만

창을 껐다가 다시 들어가면 이미지들이 더 빨리 로딩되는 것을 본 적이 있을 것이다. 이게 바로 이미지 같은 데이터들이

캐시에 저장되어 있기 때문! 그래서 캐시를 지우면 다시 이미지들을 로딩해야하기 때문에

좀 더 느리게 표시되고 버벅이는 현상이 생길 수 있다.

 

NSCache

iOS, MacOS에서는 메모리 기반 캐시를 구현하기 위해 NSCache라는 클래스가 존재한다.

NSCache는 자동으로 삭제되는 Key-Value 쌍의 컬렉션을 관리한다.

NSCache를 활용해서 캐시에 저장할 수 있는 임의의 객체를 다룰 수 있다.

 

자동 삭제

NSCache는 메모리가 부족한 경우에 자동으로 관리되기 때문에 앱 메모리를 효율적으로 사용할 수 있다.

캐시의 크기를 제한해 둘 수 있고, 캐시가 메모리 제한을 초과하면 알아서 시스템이 캐시를 삭제한다.

 

NSCache는 thread safe하다.

여러 스레드에서 안전하게 엑세스할 수 있도록 설계되었기 때문에 경합상태에 대한 걱정을 하지 않아도 된다.

여러 스레드에서 동시에 캐시를 읽고 쓰더라도 내부적으로 적절한 동기화 메커니즘을 통해 데이터 무결성을 유지한다.

 

속성 설정

NSCache에서는 캐시의 총 비용 한도를 설정해서 메모리 사용을 제어할 수 있고,

Key-Value 쌍이 삭제되기 전에 유지되는 시간 제한 등을 설정할 수 있다.

 

NSCache를 활용해서 이미지를 캐싱해보자.

 

에셋에서 불러온 이미지를 캐시에 저장하고, 캐시에서 불러오고, 캐시에서 삭제하는 예시이다.

 

CacheManager

캐싱을 관리하기 위한 클래스

import SwiftUI

final class CacheManager {
    
    static let shared = CacheManager()
    private init() { }
    
    var imageCache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        //아래 제약보다 크면 캐시를 비우고 저장함
        cache.countLimit = 100
        cache.totalCostLimit = 1024 * 1024 * 100 //약 100mb
        return cache
    }()
    
    func add(image: UIImage, name: String) {
        imageCache.setObject(image, forKey: name as NSString)
        print("캐시에 저장 성공")
    }
    
    func remove(name: String) {
        imageCache.removeObject(forKey: name as NSString)
        print("캐시에서 삭제 성공")
    }
    
    func get(name: String) -> UIImage? {
        return imageCache.object(forKey: name as NSString)
    }
}

imageCache

NSCache<NSString, UIImage> 타입으로 NSString이 키, UIImage가 값이다.

*NS 붙은 타입들은 뭘까?

예전 잡스 시절 NextStep이라는 회사 이름의 약자인데, Objective-C에서 Swift로 넘어가면서

호환성을 유지하기 위해서 남아있다고 이해하면 됨.

 

cache.countLimit

캐시에 저장할 수 있는 항목의 최대 개수를 제한하는 속성.

countLimit을 초과하면 NSCache는 오래된(최근에 접근되지 않은) 항목을 자동으로 제거해서 공간을 확보한다.

 

cache.totalCostLimit

NSCache에 저장할 수 있는 총 비용의 한계

비용 -> 각 캐시 항목에 할당된 메모리 크기 or 다른 비용 등

마찬가지로 totalCostLimit을 초과하는 새로운 항목을 추가하면 NSCache는 오래된 항목을 자동으로 제거함

 

add, remove, get

NSString을 키로! (이름으로 찾는다.)

 

.setObject(_:forKey:)

NSCache에 지정된 키-값 쌍을 추가/업데이트하는 데 사용되는 메서드.

첫 번째 파라미터 - 저장할 값(NSCache에 저장됨)

두 번째 파라미터 - 해당 값에 연결된 키를 전달. 이 키를 사용해서 값에 접근 가능

 

.removeObject(forKey:)

NSCache에서 지정된 키에 해당하는 값 제거

 

.object(forKey:)

NSCache에서 지정된 키에 해당하는 값을 반환, 없으면 nil 반환

 

CacheManager를 활용해 ViewModel 형성

final class CacheViewModel: ObservableObject {
	//에셋 이미지 이름
    let imageName = "devJeans"
    //싱글톤 객체에 접근
    private let manager = CacheManager.shared
    //에셋에서 불러오는 초기 이미지
    @Published var startingImage: UIImage?
    //캐시에서 불러와서 할당
    @Published var cachedImage: UIImage?
    
    init() {
        getImageFromAssets()
    }
    
    private func getImageFromAssets() {
        startingImage = UIImage(named: imageName)
    }
    
    func saveToCache() {
        guard let image = startingImage else { return }
        manager.add(image: image, name: imageName)
    }
    
    func removeFromCache() {
        manager.remove(name: imageName)
    }
    
    func getFromCache() {
        cachedImage = manager.get(name: imageName)
    }
}

 

이제 View에서

@StateObject로 뷰모델을 초기화 해주고

각각 버튼의 액션에서 뷰모델의 메서드들을 호출해주면 된다.