// // YYMemoryCache.m // 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 #import #import #import #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