[Effective Objective-C] #50 캐시가 필요할 때 NSDictionary 보다는 NSCache 를 사용하라
출처 : Effective Objective-C
-
맥 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)를 제공한다.
이 기능은 이 객체가 자동으로 반납되면 캐시에서도 자동으로 제거된다는 것을 말한다.
-
캐시를 잘 사용하면 앱 응답 시간을 꽤 줄일 수 있다.
네트워크에서 가져오거나 디스크에서 읽는 것처럼 다시 계산하는 비용이 큰 데이터만 캐싱하라.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #52 NSTimer 가 타깃을 리테인한다는 사실을 기억하라 (0) | 2017.10.20 |
---|---|
[Effective Objective-C] #51 initialize 와 load 메서드는 간결하게 만들라 (0) | 2017.10.19 |
[Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라 (0) | 2017.10.17 |
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #41 ~ #48 (0) | 2017.10.16 |
[Effective Objective-C] #48 반복문에는 블록 열거를 사용하라 (0) | 2017.10.15 |
댓글