본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #50 캐시가 필요할 때 NSDictionary 보다는 NSCache 를 사용하라

by 돼지왕 왕돼지 2017. 10. 18.
반응형

 [Effective Objective-C] #50 캐시가 필요할 때 NSDictionary 보다는 NSCache 를 사용하라


출처 : Effective Objective-C

autopurging data, beginContentAccess, cost limit, cost metric, count limit, endContentAccess, evictsObjectWithDiscardedContent, IOS, isContentDiscarded, LRU, lru cache, memory 사용량, Metric, nacache non copy retain, nscache, nscache hooking, nscache lru cache, nscache nspurgeabledata, nscache thread-safe, NSData, NSDictionary, nsdictionary cache, nsdictionary thread-safe, nsdiscardablecontent, NSMutableData, NSPurgeableData, OSX, prune, pruning behavior, purge reference count, retain, [Effective Objective-C] #50 캐시가 필요할 때 NSDictionary 보다는 NSCache 를 사용하라, 개수 제한, 객체 비용 제한, 객체 수 제한, 객체 제거, 메모리 반납 기능, 메모리 부족 경고, 메모리 정리, 보조 참조, 비용 지표, 중첩, 캐시, 캐싱, 키 복사


-

맥 OSX 또는 iOS 앱을 개발할 때 대부분의 경우 캐싱을 어떻게 구현할지 정해야 한다.

첫 번재로 해볼만한 방법은 사전을 이용해 메모리에 저장하는 것이다.

경험이 없는 개발자는 간단하게 NSDictionary 를 사용할 것이다.

많이 사용되는 클래스이기 때문이다.

그러나 Foundation 프레임워크의 NSCache 가 정확히 이런 용도로 설계되었기 때문에 이 클래스를 사용하는 것이 훨씬 좋다.



-

NSDictionary 에 비해 NSCache 가 지닌 장점은 시스템 메모리가 꽉 차면 자동으로 캐시의 메모리가 정리된다는 것이다.

사전을 캐시 용도로 사용하면 메모리가 부족하다는 시스템 경고를 받았을 때 사전이 이용한 메모리를 정리하는 코드를 직접 작성해야 한다.

그러나 NSCache 는 이런 일을 자동으로 해준다.

NSCache 가 Foundation 프레임워크에 속해 있기 때문에 여러분은 할 수 없는 시스템 깊숙한 곳에 접근할 수 있다. (  hooking )

또한 NSCache 는 이런 일을 자동으로 해준다.

NSCache 는 최근에 가장 적게 사용된 객체를 먼저 정리할 것이다.

사전을 이용하는 코드를 이렇게 동작하게 만드는 것은 매우 복잡한 일이다.



-

NSCache 는 키를 복사하지 않고 리테인한다.

이는 NSDictionary 에서도 할 수 있지만 훨씬 복잡한 코드가 필요하다.

캐시는 일반적으로 키를 복사하지 않는다.

키가 복사를 지원하지 않는 객체일 수 있기 때문이다.

NSCache 의 기본 설정이 복사를 하지 않는 것이기 때문에 복사를 지원하지 않는 객체와 잘 동작한다.



-

NSDictionary 는 스레드 안전하지 않지만, NSCache 는 스레드 안전하다.

이는 NSCache 의 경우 동시에 여러 스레드가 각자 락을 잡을 필요가 없음을 의미한다.

스레드 안전하다는 것은 특히 캐시에서 유용한데 여러분이 한 개의 스레드만 읽을 수 있도록 하기를 원할 수 있기 때문이다.



-

캐시는 자신의 내용물을 제거(prune)하려 할 때 그 방법을 제어할 수 있다.

사용자가 제어할 수 있는 두 가지 시스템 리소스 지표(metric)가 있는데 캐시가 가질 수 있는 객체 수를 제한하는 것과 객체의 전체적인 ‘비용’을 제한하는 것이다.

각 객체는 캐시에 추가될 때 선택적으로 비용을 부여받을 수 있다.

객체의 총 개수가 개수 제한을 넘기거나 총 비용이 비용 제한을 넘길 때 캐시는 가용 시스템 메모리가 부족해질 때 하는 것처럼 객체를 제거할 수 있다.


그러나 이는 ‘객체를 제거한다’ 가 아니라 ‘제거할 수도 있다’ 임을 꼭 알고 있어야 한다.

객체가 제거되는 순서를 정하는 방법은 구현에 따라 다르다.

이는 특정 순서에 있는 객체를 강제로 제거하기 위해 비용 지표(cost metric)을 조작해도 원하는 대로 동작하지 않는다는 것을 말한다.



-

비용 지표는 객체가 캐시에 추가될 때만 사용해야 하는데 비용을 계산하는 것이 매우 쌀 때만(즉 CPU 같은 리소스를 적게 쓰는 연산) 사용해야 한다.

비용을 계산하는 것이 비싸다면 캐시 사용을 보류해야 할 수도 있다.

객체를 캐싱할 때마다 추가로 무거운 계산을 해야 하기 때문이다.

무엇보다 캐시는 앱 응답 시간을 줄이는 데 도움을 주기 위해 사용된다.

파일 크기를 알아보기 위해 디스크에 접근하거나 비용을 알아보기 위해 DB 에 접근하는 것은 매우 좋지 않은 예다.


NSData 객체가 캐시에 추가된다면 비용 계산은 쉽다.

이 경우 데이터 크기가 바로 비용인데 이미 NSData 객체가 알고 있다.

비용을 계산하는 것은 간단히 프로퍼티를 읽기만 하면 된다.



-

Cache 예제 코드

NSCache *_cache = [NSCache new];

_cache.countLimit = 100;

_cache.totalCostLimit = 5 * 1024 * 1024;


[_cache setObject:data forKey:url cost:data.length];



-

NSCache 와 함께 사용하면 효과적인 클래스는 NSPurgeableData 다.

이 클래스는 NSDiscardableContent 프로토콜을 구현한 NSMutableData 의 하위 클래스다.

이 프로토콜은 필요한 순간에 사용하던 메모리를 반납해야 하는 객체를 위한 인터페이스를 정의한다.

이는 NSPurgeableData 가 사용하는 메모리는 시스템 리소스의 여유가 줄어들면 해제될 수 있음을 의미한다.

NSDiscardableContent 프로토콜의 isContentDiscarded 메서드가 호출되면 메모리를 반납했는지 여부를 반환한다.



-

반납될 수도 있는 데이터 객체에 접근해야 한다면 사용하는 동안 반납되는 걸 막기 위해 beginContentAccess 를 호출해야 한다.

그리고 객체 사용이 끝났을 경우 이제 원할 때 객체의 메모리를 반납해도 된다고 알리는 endContentAccess 를 호출하면 된다.

이 메서드 호출들은 중첩될 수 있다.

그래서 이는 증가/감소되는 참조 세기와 비슷하게 생각할 수 있다.

참조 수가 0이 되었을 때만 객체가 제거될 수 있다.



-

NSPurgeableData 객체가 NSCache 에 추가되었다면 반납될 수 있는 데이터 객체가 반납되면 자동으로 캐시에서 제거된다.

이는 캐시의 evictsObjectWithDiscardedContent 프로퍼티를 이용해 선택적으로 사용하거나 사용하지 않을 수 있다.



-

반납될 수 있는 데이터 객체를 생성하면 +1 반납 참조 수(purge reference count)가 반환된다.

그래서 따로 beginContentAccess 를 호출할 필요가 없다.

그러나 endContentAccess 를 호출해 +1을 0으로 만들어야 한다.(그래야 이 객체가 필요할 때 자동으로 반납된다.)




기억할 점


-

캐시로 사용한 NSDictionary 를 NSCache 로 변경하는 것을 고려하라.

캐시는 최적화된 메모리 반납 기능(pruning behavior)을 제공하고 스레드 안전할 뿐 아니라 사전처럼 키를 복사하지 않는다.



-

객체가 캐시에서 제거될 기준 지표로 수 제한(count limit)과 비용 제한(cost limit)을 사용하라.

그러나 그러한 지표를 너무 곧이곧대로 의지해서는 안 된다.

이 지표들은 단지 캐시가 보조로 참조하는 값일 뿐이다.



-

캐시를 NSpurgeableData 객체와 함께 사용하라.

이 객체는 자동 반납되는 데이터(autopurging data)를 제공한다.

이 기능은 이 객체가 자동으로 반납되면 캐시에서도 자동으로 제거된다는 것을 말한다.



-

캐시를 잘 사용하면 앱 응답 시간을 꽤 줄일 수 있다.

네트워크에서 가져오거나 디스크에서 읽는 것처럼 다시 계산하는 비용이 큰 데이터만 캐싱하라.




반응형

댓글