123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- //
- // YYMemoryCache.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/2/7.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYMemoryCache.h"
- #import <UIKit/UIKit.h>
- #import <CoreFoundation/CoreFoundation.h>
- #import <QuartzCore/QuartzCore.h>
- #import <pthread.h>
- #if __has_include("YYDispatchQueuePool.h")
- #import "YYDispatchQueuePool.h"
- #endif
- #ifdef YYDispatchQueuePool_h
- static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
- return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
- }
- #else
- static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
- return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
- }
- #endif
- /**
- A node in linked map.
- Typically, you should not use this class directly.
- */
- @interface _YYLinkedMapNode : NSObject {
- @package
- __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
- __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
- id _key;
- id _value;
- NSUInteger _cost;
- NSTimeInterval _time;
- }
- @end
- @implementation _YYLinkedMapNode
- @end
- /**
- A linked map used by YYMemoryCache.
- It's not thread-safe and does not validate the parameters.
-
- Typically, you should not use this class directly.
- */
- @interface _YYLinkedMap : NSObject {
- @package
- CFMutableDictionaryRef _dic; // do not set object directly
- NSUInteger _totalCost;
- NSUInteger _totalCount;
- _YYLinkedMapNode *_head; // MRU, do not change it directly
- _YYLinkedMapNode *_tail; // LRU, do not change it directly
- BOOL _releaseOnMainThread;
- BOOL _releaseAsynchronously;
- }
- /// Insert a node at head and update the total cost.
- /// Node and node.key should not be nil.
- - (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
- /// Bring a inner node to header.
- /// Node should already inside the dic.
- - (void)bringNodeToHead:(_YYLinkedMapNode *)node;
- /// Remove a inner node and update the total cost.
- /// Node should already inside the dic.
- - (void)removeNode:(_YYLinkedMapNode *)node;
- /// Remove tail node if exist.
- - (_YYLinkedMapNode *)removeTailNode;
- /// Remove all node in background queue.
- - (void)removeAll;
- @end
- @implementation _YYLinkedMap
- - (instancetype)init {
- self = [super init];
- _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
- _releaseOnMainThread = NO;
- _releaseAsynchronously = YES;
- return self;
- }
- - (void)dealloc {
- CFRelease(_dic);
- }
- - (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
- CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
- _totalCost += node->_cost;
- _totalCount++;
- if (_head) {
- node->_next = _head;
- _head->_prev = node;
- _head = node;
- } else {
- _head = _tail = node;
- }
- }
- - (void)bringNodeToHead:(_YYLinkedMapNode *)node {
- if (_head == node) return;
-
- if (_tail == node) {
- _tail = node->_prev;
- _tail->_next = nil;
- } else {
- node->_next->_prev = node->_prev;
- node->_prev->_next = node->_next;
- }
- node->_next = _head;
- node->_prev = nil;
- _head->_prev = node;
- _head = node;
- }
- - (void)removeNode:(_YYLinkedMapNode *)node {
- CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
- _totalCost -= node->_cost;
- _totalCount--;
- if (node->_next) node->_next->_prev = node->_prev;
- if (node->_prev) node->_prev->_next = node->_next;
- if (_head == node) _head = node->_next;
- if (_tail == node) _tail = node->_prev;
- }
- - (_YYLinkedMapNode *)removeTailNode {
- if (!_tail) return nil;
- _YYLinkedMapNode *tail = _tail;
- CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
- _totalCost -= _tail->_cost;
- _totalCount--;
- if (_head == _tail) {
- _head = _tail = nil;
- } else {
- _tail = _tail->_prev;
- _tail->_next = nil;
- }
- return tail;
- }
- - (void)removeAll {
- _totalCost = 0;
- _totalCount = 0;
- _head = nil;
- _tail = nil;
- if (CFDictionaryGetCount(_dic) > 0) {
- CFMutableDictionaryRef holder = _dic;
- _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-
- if (_releaseAsynchronously) {
- dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- CFRelease(holder); // hold and release in specified queue
- });
- } else if (_releaseOnMainThread && !pthread_main_np()) {
- dispatch_async(dispatch_get_main_queue(), ^{
- CFRelease(holder); // hold and release in specified queue
- });
- } else {
- CFRelease(holder);
- }
- }
- }
- @end
- @implementation YYMemoryCache {
- pthread_mutex_t _lock;
- _YYLinkedMap *_lru;
- dispatch_queue_t _queue;
- }
- - (void)_trimRecursively {
- __weak typeof(self) _self = self;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
- __strong typeof(_self) self = _self;
- if (!self) return;
- [self _trimInBackground];
- [self _trimRecursively];
- });
- }
- - (void)_trimInBackground {
- dispatch_async(_queue, ^{
- [self _trimToCost:self->_costLimit];
- [self _trimToCount:self->_countLimit];
- [self _trimToAge:self->_ageLimit];
- });
- }
- - (void)_trimToCost:(NSUInteger)costLimit {
- BOOL finish = NO;
- pthread_mutex_lock(&_lock);
- if (costLimit == 0) {
- [_lru removeAll];
- finish = YES;
- } else if (_lru->_totalCost <= costLimit) {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- if (finish) return;
-
- NSMutableArray *holder = [NSMutableArray new];
- while (!finish) {
- if (pthread_mutex_trylock(&_lock) == 0) {
- if (_lru->_totalCost > costLimit) {
- _YYLinkedMapNode *node = [_lru removeTailNode];
- if (node) [holder addObject:node];
- } else {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- } else {
- usleep(10 * 1000); //10 ms
- }
- }
- if (holder.count) {
- dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- [holder count]; // release in queue
- });
- }
- }
- - (void)_trimToCount:(NSUInteger)countLimit {
- BOOL finish = NO;
- pthread_mutex_lock(&_lock);
- if (countLimit == 0) {
- [_lru removeAll];
- finish = YES;
- } else if (_lru->_totalCount <= countLimit) {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- if (finish) return;
-
- NSMutableArray *holder = [NSMutableArray new];
- while (!finish) {
- if (pthread_mutex_trylock(&_lock) == 0) {
- if (_lru->_totalCount > countLimit) {
- _YYLinkedMapNode *node = [_lru removeTailNode];
- if (node) [holder addObject:node];
- } else {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- } else {
- usleep(10 * 1000); //10 ms
- }
- }
- if (holder.count) {
- dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- [holder count]; // release in queue
- });
- }
- }
- - (void)_trimToAge:(NSTimeInterval)ageLimit {
- BOOL finish = NO;
- NSTimeInterval now = CACurrentMediaTime();
- pthread_mutex_lock(&_lock);
- if (ageLimit <= 0) {
- [_lru removeAll];
- finish = YES;
- } else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- if (finish) return;
-
- NSMutableArray *holder = [NSMutableArray new];
- while (!finish) {
- if (pthread_mutex_trylock(&_lock) == 0) {
- if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
- _YYLinkedMapNode *node = [_lru removeTailNode];
- if (node) [holder addObject:node];
- } else {
- finish = YES;
- }
- pthread_mutex_unlock(&_lock);
- } else {
- usleep(10 * 1000); //10 ms
- }
- }
- if (holder.count) {
- dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- [holder count]; // release in queue
- });
- }
- }
- - (void)_appDidReceiveMemoryWarningNotification {
- if (self.didReceiveMemoryWarningBlock) {
- self.didReceiveMemoryWarningBlock(self);
- }
- if (self.shouldRemoveAllObjectsOnMemoryWarning) {
- [self removeAllObjects];
- }
- }
- - (void)_appDidEnterBackgroundNotification {
- if (self.didEnterBackgroundBlock) {
- self.didEnterBackgroundBlock(self);
- }
- if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
- [self removeAllObjects];
- }
- }
- #pragma mark - public
- - (instancetype)init {
- self = super.init;
- pthread_mutex_init(&_lock, NULL);
- _lru = [_YYLinkedMap new];
- _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
-
- _countLimit = NSUIntegerMax;
- _costLimit = NSUIntegerMax;
- _ageLimit = DBL_MAX;
- _autoTrimInterval = 5.0;
- _shouldRemoveAllObjectsOnMemoryWarning = YES;
- _shouldRemoveAllObjectsWhenEnteringBackground = YES;
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
-
- [self _trimRecursively];
- return self;
- }
- - (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
- [_lru removeAll];
- pthread_mutex_destroy(&_lock);
- }
- - (NSUInteger)totalCount {
- pthread_mutex_lock(&_lock);
- NSUInteger count = _lru->_totalCount;
- pthread_mutex_unlock(&_lock);
- return count;
- }
- - (NSUInteger)totalCost {
- pthread_mutex_lock(&_lock);
- NSUInteger totalCost = _lru->_totalCost;
- pthread_mutex_unlock(&_lock);
- return totalCost;
- }
- - (BOOL)releaseOnMainThread {
- pthread_mutex_lock(&_lock);
- BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
- pthread_mutex_unlock(&_lock);
- return releaseOnMainThread;
- }
- - (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
- pthread_mutex_lock(&_lock);
- _lru->_releaseOnMainThread = releaseOnMainThread;
- pthread_mutex_unlock(&_lock);
- }
- - (BOOL)releaseAsynchronously {
- pthread_mutex_lock(&_lock);
- BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
- pthread_mutex_unlock(&_lock);
- return releaseAsynchronously;
- }
- - (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
- pthread_mutex_lock(&_lock);
- _lru->_releaseAsynchronously = releaseAsynchronously;
- pthread_mutex_unlock(&_lock);
- }
- - (BOOL)containsObjectForKey:(id)key {
- if (!key) return NO;
- pthread_mutex_lock(&_lock);
- BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
- pthread_mutex_unlock(&_lock);
- return contains;
- }
- - (id)objectForKey:(id)key {
- if (!key) return nil;
- pthread_mutex_lock(&_lock);
- _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
- if (node) {
- node->_time = CACurrentMediaTime();
- [_lru bringNodeToHead:node];
- }
- pthread_mutex_unlock(&_lock);
- return node ? node->_value : nil;
- }
- - (void)setObject:(id)object forKey:(id)key {
- [self setObject:object forKey:key withCost:0];
- }
- - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
- if (!key) return;
- if (!object) {
- [self removeObjectForKey:key];
- return;
- }
- pthread_mutex_lock(&_lock);
- _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
- NSTimeInterval now = CACurrentMediaTime();
- if (node) {
- _lru->_totalCost -= node->_cost;
- _lru->_totalCost += cost;
- node->_cost = cost;
- node->_time = now;
- node->_value = object;
- [_lru bringNodeToHead:node];
- } else {
- node = [_YYLinkedMapNode new];
- node->_cost = cost;
- node->_time = now;
- node->_key = key;
- node->_value = object;
- [_lru insertNodeAtHead:node];
- }
- if (_lru->_totalCost > _costLimit) {
- dispatch_async(_queue, ^{
- [self trimToCost:_costLimit];
- });
- }
- if (_lru->_totalCount > _countLimit) {
- _YYLinkedMapNode *node = [_lru removeTailNode];
- if (_lru->_releaseAsynchronously) {
- dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- [node class]; //hold and release in queue
- });
- } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [node class]; //hold and release in queue
- });
- }
- }
- pthread_mutex_unlock(&_lock);
- }
- - (void)removeObjectForKey:(id)key {
- if (!key) return;
- pthread_mutex_lock(&_lock);
- _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
- if (node) {
- [_lru removeNode:node];
- if (_lru->_releaseAsynchronously) {
- dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
- dispatch_async(queue, ^{
- [node class]; //hold and release in queue
- });
- } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [node class]; //hold and release in queue
- });
- }
- }
- pthread_mutex_unlock(&_lock);
- }
- - (void)removeAllObjects {
- pthread_mutex_lock(&_lock);
- [_lru removeAll];
- pthread_mutex_unlock(&_lock);
- }
- - (void)trimToCount:(NSUInteger)count {
- if (count == 0) {
- [self removeAllObjects];
- return;
- }
- [self _trimToCount:count];
- }
- - (void)trimToCost:(NSUInteger)cost {
- [self _trimToCost:cost];
- }
- - (void)trimToAge:(NSTimeInterval)age {
- [self _trimToAge:age];
- }
- - (NSString *)description {
- if (_name) return [[NSString alloc] initWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
- else return [[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self];
- }
- @end
|