源码解析iOS 深度好文收藏ios

YYMemoryCache刨根问底

2016-06-15  本文已影响466人  js丶

前言

YYMemoryCache缓存内部用双向链表和 NSDictionary 实现了 LRU 淘汰算法,使用pthread_mutex来保证线程安全 ,解析源码之前,先了解一下相关内存置换算法:

** _YYLinkedMapNode:结点结构:**

@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

** 知识点:**

** _YYLinkedMap:链表结构**

@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++;
        
    
    /** 提供双向循环链表:添加节点思路:这里有两种方法!:
    从前驱线索到后继线索链接:
     head->_prev->_next = node;
     node->_next = head;
     
    
     node->_next = head;
     head->_prev = node;
     
     */
    if (_head) {
        
        // 把插入的数据作为头结点
        node->_next = _head;
        // 设置第二个节点(原先头结点的)的前继
        _head->_prev = node;
        // 设置头节点为插入的节点
        _head = node;
    } else { // 如果插入的是第一个数据
        _head = _tail = node;
    }
}

// node移到head(头结点) 每次缓存命中时将页面转移LRU位置(head)
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    // 如果该结点是头结点,直接返回
    if (_head == node) return;
    
    
    
    if (_tail == node) {
        // 因为最后一个节点要往(head)头节点移动所以尾节点要指向前一个节点
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        /**  如果该结点位于链表之间
         */
        // 当前结点的前驱线索指向当前结点的后继
        node->_next->_prev = node->_prev;
        // 当前结点的后继线索指向前驱
        node->_prev->_next = node->_next;
    }
    // 当前结点的后继线索指向头结点,那么head为第二结点
    node->_next = _head;
    // 非循环双向链表,将前继线索置为nil
    node->_prev = nil;
    // 此时的head作为第二个结点,第二个结点前驱线索指向第一个节点
    _head->_prev = node;
    // 头结点指针指向第一个节点
    _head = node;
    
    /** 如果是双向循环链表:取消 node->_prev = nil; 这行代码
     head->_prev->_next = node;
     node->_next = head;

     */
}

/// 移除缓存池节点和更新的总成本。节点在已经DIC内。
- (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

** 补充知识点 **

主要思想:

MRU淘汰算法
- (_YYLinkedMapNode *)removeHeadNode {
if (!_head) return nil;
_YYLinkedMapNode *head = _head; // 记录头结点
CFDictionaryRemoveValue(_dic, (_bridge const void *)(head->_key));
// 更新的总成本和数据量
_totalCost -= _head->_cost;
_totalCount--;
if (_head == _tail) {
_head = _tail = nil;
} else {

        // 设置头节点指向后一个节点
        _head = _head->_next;
        // 设置投节点的前驱线索置为nil。
        _head->_prev = nil;
    }
    // 返回头节点 head是原先没有头结点
    return head;
}

YYMemoryCache 内存缓存

@implementation YYMemoryCache {
    pthread_mutex_t _lock;
     // LRU->dic相当于缓存池
    _YYLinkedMap *_lru;
    dispatch_queue_t _queue;
}
// trimRecursively修剪递归
- (void)_trimRecursively {
    // 1.防止Block的循环引用, wself是为了block不持有self,避免循环引用,
    __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), ^{
        // 2.防止多线程和arc环境下弱引用提前释放,加上__strong可以保证运行block期间_self不会被释放
        __strong typeof(_self) self = _self;
        if (!self) return;
        [self _trimInBackground];
        [self _trimRecursively];
    });
}

- (void)_trimInBackground {
    
    // 使用线程异步执行方式,将block作为一个任务添加到串行队列处理,
    dispatch_async(_queue, ^{
        [self _trimToCost:self->_costLimit];
        [self _trimToCount:self->_countLimit];
        [self _trimToAge:self->_ageLimit];
    });
}

// 修剪Cost
- (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) {
        /**
         函数是pthread_mutex_lock函数的非阻塞版本。如果mutex参数所指定的互斥锁已经被锁定的话,调用pthread_mutex_trylock函数不会阻塞当前线程,而是立即返回一个值来描述互斥锁的状况。
         */
        // pthread_mutex_trylock:非阻塞的锁定互斥锁。函数成功返回0。任何其他返回值都表示错误。
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {  
                
                // 获取最新尾节点,在移除尾节点的同时,重新设置_totalCost总byte数据
                _YYLinkedMapNode *node = [_lru removeTailNode]; // 首先这里我已经保证尾结点
                                
                // 如果尾节点存在,添加到Holder数组
                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)
    {
        //如果_lru->_tail不为空,也就是说如果有数据;设置缓存中对象的最大过期时间(这里作者默认设置为一周) 也就是当前的时间 - 尾结点的时间(尾结点也就是最晚使用的结点) 如果小于一周,缓存对象不需要修剪。
        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];
                // 添加那些最近从最近最多使用链表中淘汰的页面信息 (Ghost list for LRU)
                [_lruGhost insertNodeAtHead:node];
                
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            /** usleep使用
             作者回答:为了尽量保证所有对外的访问方法都不至于阻塞,这个对象移除的方法应当尽量避免与其他访问线程产生冲突。当然这只能在很少一部分使用场景下才可能有些作用吧,而且作用可能也不明显。。。
             */
            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];
}

// 设置新结点放置在head,key是图片的名称
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    if (!key) return;
    if (!object) {
        // 如果针对图片的URL(Key)设置image(object)为nil,则移除缓存(隐藏条件)
        [self removeObjectForKey:key];
        return;
    }
    pthread_mutex_lock(&_lock);
 
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    
    
    //
    /** 设置对象关注点
     热端-----------------冷端
     与标准的 LRU 一样,添加数据插入到热端的头
     
     添加过程中,判断如果缓存中对象开销大于用户设置对象开销,修剪(淘汰旧对象),具体修剪过程可以看trimToCost方法描述
     接着继续判断缓存中对象总数 > 用户设置对象总数,修剪(淘汰旧对象)
     */
    
    // 返回当前的绝对时间,在几秒钟内
    NSTimeInterval now = CACurrentMediaTime();
    if (node) { // 如果缓存池中存在该结点,并且清空原开销,重新设置time(当前设置时间)为,因为缓存池对象作者默认设置为一周,如果该对象重新使用,需要重新设置当时使用时间;接着重新设置cost;
        // _cost:位图图像的字节数,重新设置cost,把该节点置于Head位置上
        _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) {
            //  主队列或者新建一个低优先级的NSQualityOfServiceUtility        —-   DISPATCH_QUEUE_PRIORITY_LOW  队列执行
            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]; 这种在queue上调用对象的方法,能保证node是在这个queue上release掉,这个确定么?   
                 作者回答:你可以给 node 加个 dealloc 下断点看看。
                 群众回答:应该是node在执行完这个方法后就出了作用域了,reference会减1,但是此时node不会被dealloc,因为block 中retain了node,使得node的reference count为1,当执完block后,node的reference count又-1,此时node就会在block对应的queue上release了。的确很巧妙
                 */
                [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) {
        [_lruGhost insertNodeAtHead: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 stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
    else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}

@end

知识点

图片来自作者博客,地址在上头

可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的。有消息称,苹果在新系统中已经优化了 pthread_mutex 的性能


[node class]; 这种在queue上调用对象的方法,能保证node是在这个queue上release掉,这个确定么?
作者回答:你可以给 node 加个 dealloc 下断点看看。
群众回答:应该是node在执行完这个方法后就出了作用域了,reference会减1,但是此时node不会被dealloc,因为block 中retain了node,使得node的reference count为1,当执完block后,node的reference count又-1,此时node就会在block对应的queue上release了,的确很巧妙

** usleep使用**
作者回答:为了尽量保证所有对外的访问方法都不至于阻塞,这个对象移除的方法应当尽量避免与其他访问线程产生冲突。当然这只能在很少一部分使用场景下才可能有些作用吧,而且作用可能也不明显。。。

最后知识点总结


** 添加: **

void CFDictionarySetValue(CFMutableDictionaryRef theDict, const void *key, const void *value);

** explain:**

@param key The key of the value to set into the dictionary. If a key
which matches this key is already present in the dictionary, only
the value is changed ("add if absent, replace if present"). If
no key matches the given key, the key-value pair is added to the
dictionary. If added, the key is retained by the dictionary,
using the retain callback provided

如果参数Key已经存在于字典,替换旧值,如果参数Key不存在于字典,以键值对形式添加至字典

** explain:**

void CFDictionaryAddValue(CFMutableDictionaryRef theDict, const void *key, const void *value);

如果参数Key已经存在于字典,不做任何操作,如果参数Key不存在于字典,以键值对形式添加至字典

** 遍历字典函数: **

void CFDictionaryApplyFunction(CFDictionaryRef theDict, CFDictionaryApplierFunction applier, void *context);

explain

在字典中调用每个键值对的函数。
如果遍历的是一个可变的集合,它可以保证改变集合的内容安全,使用CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类可以带来不少性能提升

最后

读了代码还有之前了解一点Runtime知识,objc_cache方法链表作用于方法调用的性能优化(存储近期使用的方法缓存结构),其中包含最近使用的方法指针,以及UITableView的重用机制,他们共同的特征是把使用的过方法或cell存入缓存,再次使用时从缓存取出,于是又结合了看了ARC算法,结合YYMemoryCache源码实践一番(可选LRU/类似ARC/MRU算法),也许你看了源码ARC算法不是有4个链表吗,如果再次添加数据时已存在数据时,结点前置(LRU位置),所以我就用了2个链表来操作数据,LRUGhost链表来存储移除的数据,再次添加时如果LRU没有相关的数据时再从LRUGhost链表中查找,以及LRUGhost链表出现内存警告时也做了一些处理,效率简单测试了一下,还是LRU的好,可能是我过多判断和操作了,有空的朋友可以测试测试,项目源代码贴在下面

LTMemoryCache.h

typedef NS_OPTIONS(NSUInteger, LTCacheListType) {
    LTCacheListTypeLRU = 0,
    LTCacheListTypeMRU = 1 << 0,
    LTCacheListTypeLRUGhost = 1 << 1,
};

LTMemoryCache.m

//
//  LTMemoryCache.m
//  LTMemoryCache
//
//  Created by lmj  on 16/6/12.
//  Copyright © 2016年 linmingjun. All rights reserved.
//

#import "LTMemoryCache.h"
#import <UIKit/UIKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <QuartzCore/QuartzCore.h>
#import <pthread.h>

static inline dispatch_queue_t LTMemoryCacheGetReleaseQueue() {
    return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}

@interface _LTLinkedMapNode : NSObject {
    @package
    __unsafe_unretained _LTLinkedMapNode *_prev; // retained by dic
    __unsafe_unretained _LTLinkedMapNode *_next; // retained by dic
    id _key;
    id _value;
    NSUInteger _cost;
    NSTimeInterval _time;
}
@end

@implementation _LTLinkedMapNode
@end

@interface _LTLinkedMap : NSObject {
    
    @package
    CFMutableDictionaryRef _dic;
    LTCacheListType _type;
    NSUInteger _totalCost;
    NSUInteger _totalCount;
    _LTLinkedMapNode *_head;
    _LTLinkedMapNode *_tail;
    BOOL _releaseOnMainThread;
    BOOL _releaseAsynchronously;
}


- (void)insertNodeAtHead:(_LTLinkedMapNode *)node;


- (void)bringNodeToHead:(_LTLinkedMapNode *)node;

- (void)removeNode:(_LTLinkedMapNode *)node;

- (_LTLinkedMapNode *)removeTailNode;

- (void)removeAll;

@end

@implementation _LTLinkedMap

- (instancetype)init {
    self = [super init];
    _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    _releaseOnMainThread = NO;
    _releaseAsynchronously = YES;
    return self;
}




- (void)dealloc {
    CFRelease(_dic);
}

- (void)insertNodeAtHead:(_LTLinkedMapNode *)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;
    }
}

// node移到head(头结点) 每次缓存命中时将页面转移LRU位置(head)
- (void)bringNodeToHead:(_LTLinkedMapNode *)node {
    // 如果该结点是头结点,直接返回
    if (_head == node) return;
    if (_tail == node) {
        // 因为最后一个节点要往(head)头节点移动所以尾节点要指向前一个节点
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        /**  如果该结点位于链表之间
         */
        // 当前结点的前驱线索指向当前结点的后继
        node->_next->_prev = node->_prev;
        // 当前结点的后继线索指向前驱
        node->_prev->_next = node->_next;
    }
    // 当前结点的后继线索指向头结点,那么head为第二结点
    node->_next = _head;
    // 非循环双向链表,将前继线索置为nil
    node->_prev = nil;
    // 此时的head作为第二个结点,第二个结点前驱线索指向第一个节点
    _head->_prev = node;
    // 头结点指针指向第一个节点
    _head = node;
    /** 如果是双向循环链表:取消 node->_prev = nil; 这行代码
     head->_prev->_next = node;
     node->_next = head;
     */
}

/// 移除内节点和更新的总成本。节点在已经DIC内。
- (void)removeNode:(_LTLinkedMapNode *)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;
}


- (_LTLinkedMapNode *)removeTailNode {
    if (!_tail) return nil;
    _LTLinkedMapNode *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;
}

- (_LTLinkedMapNode *)removeHeadNode {
    if (!_head) return nil;
    _LTLinkedMapNode *head = _head; // 记录头结点
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(_head->_key));
    // 更新的总成本和数据量
    _totalCost -= _head->_cost;
    _totalCount--;
    if (_head == _tail) {
        _head = _tail = nil;
    } else {
        
        // 设置头节点指向后一个节点
        _head = _head->_next;
        // 设置头节点的前驱线索置为nil。
        _head->_prev = nil;
    }
    // 返回头节点
    return head;
}


- (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() : LTMemoryCacheGetReleaseQueue();
            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 LTMemoryCache {
    pthread_mutex_t _lock;
    _LTLinkedMap *_lru;
    _LTLinkedMap *_lruGhost;
    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];
    });
}

#pragma mark - remove/setObjectTrim

- (void)removeAllList {
    switch (LTCacheListTypeLRU) {
        case LTCacheListTypeLRUGhost :
            [_lru removeAll];
            [_lruGhost removeAll];
            break;
        case LTCacheListTypeMRU :
        case LTCacheListTypeLRU :
            [_lru removeAll];
            break;
        default:
            break;
    }
}

- (_LTLinkedMapNode *)removeHeadOrTail {
    _LTLinkedMapNode *node = [_lru removeTailNode];
    switch (_type) {
        case LTCacheListTypeLRUGhost :
            [_lruGhost insertNodeAtHead:node];
            break;
        case LTCacheListTypeMRU :
            node = [_lru removeHeadNode];
        case LTCacheListTypeLRU :
        default:
            break;
    }
    return node;
}

- (void)setObjectLruOrLruGhost:(_LTLinkedMap *)_LruOrLruGhost {
    if (_LruOrLruGhost->_totalCost > _costLimit) {
        dispatch_async(_queue, ^{
            [self trimToCost:_costLimit];
        });
    }
    if (_LruOrLruGhost->_totalCount > _countLimit) {
        
        _LTLinkedMapNode * node = [_LruOrLruGhost removeTailNode];

        if ([_LruOrLruGhost isEqual:_lru]) {
            [_lruGhost insertNodeAtHead:node];
           
        } else {
            if (_lruGhost->_totalCount == _countLimit) {
               
                [_lruGhost removeAll];
                
            }
        }
        if (_LruOrLruGhost->_releaseAsynchronously) {
            dispatch_queue_t queue = _LruOrLruGhost->_releaseOnMainThread ? dispatch_get_main_queue() : LTMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [node class];
            });
        } else if (_LruOrLruGhost->_releaseOnMainThread && !pthread_main_np()) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [node class];
            });
        }
    }
}



- (void)_trimToCost:(NSUInteger)costLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (costLimit == 0) {
        
        [self removeAllList];
        
        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) {
                _LTLinkedMapNode *node = [self removeHeadOrTail];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000);
        }
    }
    
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : LTMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
          
            [holder count];
        });
    }
}

- (void)_trimToCount:(NSUInteger)countLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (countLimit == 0) {
        [self removeAllList];
        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) {
                _LTLinkedMapNode *node = [self removeHeadOrTail];
                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() : LTMemoryCacheGetReleaseQueue();
        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) {
        [self removeAllList];
        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) {
                _LTLinkedMapNode *node = [self removeHeadOrTail];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000);
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : LTMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count];
        });
    }
}

- (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 - Init Method

- (instancetype)init {

    return [self initWithCache:LTCacheListTypeLRU];
}

- (instancetype)initWithCache:(LTCacheListType )type {
    self = [super init];
    
    pthread_mutex_init(&_lock, NULL);
    _lru = [_LTLinkedMap new];
    _lruGhost = [_LTLinkedMap new];
    _queue = dispatch_queue_create("com.cache.memory", DISPATCH_QUEUE_SERIAL);
    
//    _countLimit = NSUIntegerMax;
    _countLimit = NSUIntegerMax;
    _costLimit = NSUIntegerMax;
    _ageLimit = DBL_MAX;
    _autoTrimInterval = 5.0;
    _shouldRemoveAllObjectsOnMemoryWarning = YES;
    _shouldRemoveAllObjectsWhenEnteringBackground = YES;
    _type = type;
    [[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];
    [_lruGhost 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;
    _lruGhost->_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;
    _lruGhost->_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);
    _LTLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        node->_time = CACurrentMediaTime();
        [_lru bringNodeToHead:node];
    } else {
        node = CFDictionaryGetValue(_lruGhost->_dic, (__bridge const void *)(key));
        switch (_type) {
            case LTCacheListTypeLRUGhost :
                if (node) {
                    node->_time = CACurrentMediaTime();
                     [_lruGhost removeNode:node];
                    [_lru insertNodeAtHead:node];
                   
                }
                break;
            case LTCacheListTypeMRU :
            case LTCacheListTypeLRU :

                break;
            default:
                break;
        }
    }
    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);
    _LTLinkedMapNode *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 = CFDictionaryGetValue(_lruGhost->_dic, (__bridge const void *)(key));
        switch (_type) {
            case LTCacheListTypeLRUGhost :
                if (node) {
                    node->_time = CACurrentMediaTime();
                    [_lruGhost removeNode:node];
                    [_lru insertNodeAtHead:node];
                    
                } else {
                    node = [_LTLinkedMapNode new];
                    node->_cost = cost;
                    node->_time = now;
                    node->_key = key;
                    node->_value = object;
                    [_lru insertNodeAtHead:node];
                }
                break;
            case LTCacheListTypeMRU :
            case LTCacheListTypeLRU :
                node = [_LTLinkedMapNode new];
                node->_cost = cost;
                node->_time = now;
                node->_key = key;
                node->_value = object;
                [_lru insertNodeAtHead:node];
                break;
            default:
            break;
        }
    }
    switch (_type) {
        case LTCacheListTypeLRUGhost :
            [self setObjectLruOrLruGhost:_lru];
//            [self setObjectLruOrLruGhost:_lruGhost];
            break;
        case LTCacheListTypeMRU :
        case LTCacheListTypeLRU :
            [self setObjectLruOrLruGhost:_lru];
        default:
            break;
    }
    pthread_mutex_unlock(&_lock);
}

- (void)removeObjectForKey:(id)key {
    if (!key) return;
    pthread_mutex_lock(&_lock);
    _LTLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        
        switch (_type) {
            case LTCacheListTypeLRUGhost:
                [_lru removeNode:node];
                [_lruGhost insertNodeAtHead:node];
               
                break;
                case LTCacheListTypeMRU:
                case LTCacheListTypeLRU:
                 [_lru removeNode:node];
                break;
            default:
                break;
        }
        
        
        if (_lru->_releaseAsynchronously) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : LTMemoryCacheGetReleaseQueue();
            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];
    [_lruGhost 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 stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
    else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}

@end

源代码文件

上一篇 下一篇

猜你喜欢

热点阅读