티스토리 뷰

iOS/iOS-Memo

Cache, NSCache, URLCache

malrang-malrang 2022. 9. 30. 23:04

Cache, NSCache, URLCache

Cache(캐시)

Cache 란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 저장소를 가르킨다.
IOS 파일 시스템에서는 Library 내부에 위치하며 백업이 불가능하다.

저장 공간이 작고 비용이 비싼 대신 빠른 성능을 제공한다.

반복적으로 서버에서 동일한 데이터를 받아와야 하는경우 캐시를 활용하면 효율적이게 된다.

Cache 를 사용한다는것은 반복적으로 동일한 데이터를 서버에서 불러오는 경우 서버에 데이터를 요청하는 것이 아니라 메모리에 데이터를 저장했다가 불러다 쓰는것을 의미한다.

서버에서 데이터를 받아오는것보다 메모리에서 가져오는것이 더빠르기때문에 성능의 이점이 있다.

이때 원하는 데이터가 캐시에 존재할경우 Cache Hit 이라고하며 원하는 데이터가 캐시에 없을 경우 서버에 요청을 해야하며 이를 Cache Miss 라고 한다.

NSCache

key-value 형태의 데이터를 임시로 저장하는 데 사용할 수 있는 가변 컬렉션이다.
자원이 부족할 때 삭제 대상이 된다.

Declaration

class NSCache<KeyType, ObjectType> : NSObject where >KeyType : AnyObject, ObjectType : AnyObject

OverView

NSCache 는 다른 컬렉션과는 다른 몇 가지 특성을 가진다.

  • NSCache 클래스는 캐시가 시스템 메모리를 너무 많이 사용하지 않도록 자동 삭제 정책을 가진다. 다른 응용프로그램에메모리가 필요한 경우 이 정책은 캐시에서 일부 항목을 제거하여 메모리 사용 공간을 최소화한다.
  • 캐시를 잠글(lock) 필요 없이 별도 스레드에서 캐시 항목을 추가하고, 삭제하고 검색할 수 있다.
  • NSMutableDictionary 객체와는 달리, 캐시는 그 안에 들어있는 키 객체를 복사하지 않습니다.

일반적으로 NSCache 객체를 사용하여 생성 비용이 많이 드는 일시적인 데이터 객체를 저장한다.

값을 다시 계산할 필요가 없으므로 이러한 객체를 재사용하는 것은 성능상의 이점을 얻을 수 있다.

하지만 이러한 임시 객체들은 응용프로그램에 중요하지 않은 데이터이며, 메모리가 부족할 경우 삭제될 수 있다. 만삭제되었다면 이 값들이 필요할 때 다시 계산해야 한다.

사용되지 않을 때 삭제될 수 있는 하위 구성 요소들은 NSDiscardableContent 프로토콜을 채택하여 캐시 제거 동작향상시킬 수 있다.

기본적으로 캐시의 NSDiscardableContent 객체는 내용이 삭제된 경우 자동으로 제거되지만 이 자동 제거 정책은 변경할 있다.

NSDiscardableContent 객체가 캐시에 저장되면 캐시는 삭제 명령 수행 시 discardContentIfPossible()호출한다.

NSCache 의 프로퍼티와 메서드

  • var name: String :
    • 캐시의 이름
  • var countLimit: Int :
    • 캐시가 가질 수 있는 최대한의 객체 수
  • var totalCostLimit: Int :
    • 객체를 제거하기 전에 캐시가 보유할 수 있는 최대 비용
  • var evictsObjectsWithDiscardedContent: Bool :
    • 캐시가 내용이 삭제된 NSDiscardableContent를 자동으로 제거할지의 여부
  • protocol NSDiscardableContent :
    • 클래스의 객체의 하위 구성 요소가 사용되지 않을 때 삭제되어도 된다면 이 프로토콜을 채택함으로써 응용 프로그램메모리 사용 공간을 줄일 수 있다.
  • var delegate: NSCacheDelegate? :
    • 캐시의 위임자
  • protocol NSCacheDelegate :
    • NSCache의 위임자 객체는 이 프로토콜을 채택한다.
    • 이 프로토콜을 채택하면 캐시에서 객체가 추출되거나 제거될 때의 작업들을 특수화할 수 있다.
  • func object(forKey: KeyType) -> ObjectType? :
    • 주어진 Key과 연결된 값을 반환한다.
  • func setObject(ObjectType, forKey: KeyType) :
    • 캐시에 주어진 키와 그에 대응되는 값을 넣는다.
  • func setObject(ObjectType, forKey: KeyType, cost: Int) :
    • 캐시에 주어진 키와 그에 대응되는 값, 그리고 그 비용을 넣는다.
  • func removeObject(forKey: KeyType) :
    • 캐시에 주어진 키에 대응되는 값을 제거합니다.
  • removeAllObjects() :
    • 캐시를 비웁니다.

NSCache 의 특징

  • Caching 할 객체의 최대 수를 지정할 수 있습니다.
  • Caching Cost Limit을 정할 수 있습니다.
  • 연결 리스트와 Dictionary를 함께 사용합니다.
    • 캐싱은 중간에 있는 데이터를 추가, 삭제가 빈번하게 발생하기 때문에 배열을 사용하면 데이터를 앞으로 당기거나 미는 작업이 필요할 수 있다. 하지만 연결리스트를 사용하면 이 작업이 매우 용이하다. - 반면 연결 리스트의 경우 key-value 타입이 아니기 때문에 탐색에 O(n)이 발생되므로 동시에 Dictionary를 사용함으로써 key로 데이터를 접근할 때 O(1)으로 빠르게 탐색하게 할 수 있다.
  • limitation 을 넘어갈 경우 적은 용량(cost)의 데이터부터 삭제한다.
    • cost limitation을 먼저 체크하고 이후 count limitation을 체크합니다. NSDiscardableContent 객체의 삭제는 연결리스트를 통해서 삭제가 진행되는데 이 연결리스트는 cost로 정렬된 연결 리스트(linkedList)다. head부터 삭제가 진행되기 때문에 결과론적으로는 적은 cost의 데이터부터 삭제되게 된다.

NSCache 구현해보자!

final class ImageCacheManager {
    static let shared = ImageCacheManager()

    let cache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        cache.countLimit = 100
        return cache
    }()

    private init() {}
}

위와 같이 NSCache를 프로퍼티로 소유하는 ImageCacheManager를 singletone으로 하나 만들어 주었다!
cache프로퍼티를 보면 제네릭으로 Key의 타입과 Value의 타입을 기재해야한다. (이때 키값으로는 NSString, NSURL을 많이 사용하는듯하다.)
NSCache의 Key, Value 타입을 기재하고 초기화 해준뒤 위의 설명글에 작성된 속성중 countLimit을 사용해 이미지를 캐시에 100개까지 저장하도록 정의해주었다.

그럼 다음으로는 캐시에 이미지를 저장하거나, 저장된 이미지를 가져와 활용하는 구현 코드를 보자!

extension UIImageView {
    func setImage(with urlstring: String, placeholder: UIImage? = nil) {
        self.imageDownloadTask?.suspend()
        self.imageDownloadTask?.cancel()

        let cache = ImageCacheManager.shared.cache

        if let cacheImage = cache.object(forKey: urlstring as NSString) {
            return self.image = cacheImage
        }

        self.imageDownloadTask = ImageDownloader.default.download(with: urlstring) { result in
            switch result {
            case .success(let image):
                cache.setObject(image, forKey: urlstring as NSString)
                DispatchQueue.main.async {
                    self.image = image
                }
                return
            case .failure:
                guard let placeholder = placeholder else { return }
                DispatchQueue.main.async {
                    self.image = placeholder
                }
                return
            }
        }
    }
}

위의 코드는 UIImageView를 extension하여 구현한 코드인데 간단히 설명하자면 인자값으로 urlString을 받아 캐시에 저장된것중 인자로 전달받은 urlString과 일치하는 키값이 있을경우 Key값에 해당하는 Value를 반환받아 UIImageView의 image프로퍼티에 할당한다.
인자로 전달받은 urlString과 일치하는 Key값이 없을경우 네트워크 통신으로 이미지를 다운받은후(ImageDownloader.download) 캐시에 저장하고 image프로퍼티에 할당한다.

캐시에 저장된게 있으면 꺼내쓰고 없으면 다운로드받아서 캐시에 저장하는셈이다.

URLCache

URL 요청을 캐시된 응답 개체에 매핑하는 개체입니다.

Declaration

class URLCache : NSObject

OverView

URLCache 클래스는 NSURLRequest 객체를 CachedURLResponse 객체에 매핑함으로써 URL load 요청에 대한 응답의 캐싱을 구현한다.

또한 인메모리 및 디스크에 저장된 캐시를 제공하며 각 캐시의 크기를 모두 조절할 수 있고 캐시 데이터가 영구적으로 저장될 경로를 제어할 수 있다.

IOS 에서는 시스템 디스크 공간이 부족할 경우 앱이 실행중이지 않을때 디스크에 저장된 캐시를 삭제할수있다.

Thread Safety

iOS 8, macOS 10.10 이상에서 URLCache는 스레드 안전합니다.

URLCache 인스턴스 메서드들이 여러 실행 컨텍스트에서 호출되더라도 스레드 안전하기는 하지만, cachedResponse(for:)와 storeCachedResponse(_:for:)와 같은 메서드가 같은 요청에 대한 응답을 동시에 읽고 쓰려고 한다면 경쟁상태(race condition)를 피할 수 없으므로 조심해야 한다.

URLCache의 서브 클래스들은 반드시 이러한 상황에서 스레드 안전하게 동작할 수 있도록 메서드를 오버라이드하여 구현해야 한다.

Subclassing Notes

URLCache 클래스는 있는 그대로 사용되도록 만들어졌지만 캐시가 필요한 응답을 가려내려 하거나, 보안상의 이유로 저장 메커니즘을 재구현 하려는 등 특정한 경우에는 서브클래싱 할 필요가 생길수도 있다.

이 클래스의 메서드를 오버라이드 할 경우, 시스템이 task 파라미터를 사용하는 메서드를 그렇지 않은 메서드보다 우선시 한다는 것을 유의해야 한다.

그러므로 서브클래싱을 할때에는 다음과 같이 task 기반의 메서드를 오버라이드 해야한다.

  • response 를 캐시에 저장할때
    • task 의 storeCachedResponse(_:for:)를 오버라이드한다.
  • Cache 에서 response 를 읽어올때
    • getCachedResponse(for:completionHandler:)를 오버라이드한다.
  • Cache 에서 response 를 제거할때
    • task기반의 removeCachedResponse(for:)를 오버라이드한다.

URLCache 의 프로퍼티와 메서드

  • class var shared: URLCache :
    • 공유 URLCache 인스턴스 (싱글턴?)

캐시 객체 읽고 저장하기

  • func cachedResponse(for: URLRequest) -> CachedURLResponse? :

    • 지정된 URL 요청에 대한 캐시된 URL 응답을 반환합니다.
  • func storeCachedResponse(CachedURLResponse, for: URLRequest) :

    • 지정된 URL 요청에 대한 캐시된 URL 응답을 저장합니다.
  • func getCachedResponse(for: URLSessionDataTask, completionHandler: (CachedURLResponse?) -> Void) :

    • data task에 대한 캐시된 URL 응답을 읽어서 completion handler로 전달합니다.
      • func storeCachedResponse(CachedURLResponse, for: URLSessionDataTask) :
    • 지정된 data task에 대한 캐시된 URL 응답을 저장합니다.

    캐시 객체 삭제

    • func removeCachedResponse(for: URLRequest) :
      • 지정된 URL 요청에 대한 캐시된 URL 응답을 삭제합니다.
    • func removeCachedResponse(for: URLSessionDataTask) :
      • 지정된 data task에 대한 캐시된 URL 응답을 삭제합니다.
    • func removeCachedResponses(since: Date) :
      • 지정된 날짜 이전의 모든 캐시된 응답을 삭제합니다.
    • func removeAllCachedResponses() :
      • 수신자의 저장된 모든 캐시된 URL 응답을 삭제합니다.

    메모리에 저장된 캐시 속성 읽고 쓰기

    • var currentDiskUsage: Int :
      • 메모리에 저장된 캐시의 현재 크기를 바이트 단위로 나타냅니다.
    • var diskCapacity: Int :
      • 메모리에 저장된 캐시의 가용 용량을 바이트 단위로 나타냅니다.

    캐시 스토리지 정책

    • enum URLCache.StoragePolicy :
      • 캐싱 전략을 지정하는 상수로써 CachedURLResponse 객체를 사용한다.

생각해볼것들


1. 캐시란 무엇이고 어떤 역할일까?

데이터나 값을 미리 복사해 놓는 임시저장소를 말한다

Cache 의 역할
똑같은 데이터를 서버에서 가져오면 비효율적이기때문에 Cache 라는 임시저장소에 저장해 서버에서 반복적으로 동일한 데이터를 가져오지 않도록 하는것

단점
업데이트를 바로바로 해주지 않기에 즉각성이 떨어진다.


2. 몇 가지 기준으로 캐시를 구분해볼까?

구분 할수있는 기준은 3개로 나눌수 있다.

    1. 저장장소
    1. 데이터가 저장되는 계층에 따른 구분
    1. 읽기 및 쓰기에 따른 구분
  • 캐시는 클라이언트 서버 구분이 있나?*
    구분된다.
    클라이언트는 클라이언트만의 local 에 저장할 수 있는 캐시가 있다.

    서버는 여러 클라이언트의 요청에 대해 저장할 수 있는 캐시가 있다.
    (서버에서도 부하를 줄이기 위해 서버에 자주하는 요청을 캐싱 해둔다.)

  • 캐시는 어디어디에 저장할 수 있을까?*

    1. 메모리
    1. 디스크
    • 앱 내부 디스크 (UserDefault, CoreData)
    • 앱 외부 디스크

3. 캐시를 구현할 때 고려해야 하는 캐시 운용 정책에는 어떤것들이 있을까?

메모리(앱 내부 디스크) 에서 사용할것인지 외부디스크 에 사용을해야할지 앱내부에서 사용하게되면 앱을 종료하면 사라지게 된다.


4. AlamofireImage 라이브러리와 Kingfisher 라이브러리의 캐시 정책은 어떤 차이가 있나요?

두 라이브러리 모두 단순히 이미지 주소로부터 이미지를 다운받는 것 뿐 아니라 이미지 처리에 있어 탁월한 기능을 하지만 kingfisher는 기본적으로 캐싱을 제공하며 alamofireimage는 기본 파싱이 제공되지 않아 별도의 캐시처리가 필요하다는 차이점이 존재한다.


5. 각 라이브러리에서 캐시 기능을 지원하기 위해 활용하는 Cocoa Layer의 주요 기능(클래스, 구조체 등)은 무엇이 있나요?

  • AlamofireImage
    • URLCache, AutoPurgingImageCache
  • KingFisher
    • URLSession delegate 의 receiveData 로부터 받은 data 를 모은 다음, UIImage(data: scale:) method 사용

6. 만약 라이브러리에서 on disk cache를 지원한다면, 캐시를 저장하는 디스크 영역은 어디인가요?

라이브러리/cache

어떤곳에 파일을 저장할것인지 디렉토리 위치를 정해주어야 한다.

 //     저장될 데이터 크기(limit), 디스크에 저장할수있는 최대크기(limit), 저장될 위치 경로
 convenience init(memoryCapacity: Int, diskCapacity: Int, directory: URL? = nil)

7. 캐시를 구현할 때 고려해야 하는 캐시 운용 정책에는 어떤것들이 있을까?

  • 저장될 수 있는 크기
  • 데이터를 비워줄 기간, 크기
  • 데이터가 저장될 위치
  • 저장될 데이터 타입

8. NSCache의 용도는?

  • memory cache 사용.
  • NSCache는 Cocoa에서 사용할 수 있는 캐싱을 위한 클래스
  • NSCache는 메모리가 충분할 때는 주어진 모든 데이터를 캐싱
  • 반면, 가용 메모리가 적을 때는 다른 앱을 위해 캐싱된 데이터를 자동으로 제거한다.

9. URLCache의 기본 캐시 정책은

  • memory cache, on-disk cache 모두 사용 가능 하다.
  • 디스크의 공간이 부족할 때 삭제될 수 있고, 앱이 실행되고 있지 않을 때만 삭제.

'iOS > iOS-Memo' 카테고리의 다른 글

Launch Screen을 적용해보자!  (0) 2022.10.10
APIKey를 숨겨보자!  (0) 2022.10.09
Underline SegmanetControl  (0) 2022.08.12
NotificationCenter, Modal 사용시 주의점!  (0) 2022.04.29
tag 사용법  (0) 2022.04.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함