手写NSCache及开源实现的分析

栏目: 软件资讯 · 发布时间: 5年前

内容简介:NSCache是苹果提供的内存缓存框架。它和NSMutableDictionary和用法很相似,但NSCache是线程安全的,并且没有对Key进行copy。虽然官方文档中没有明确指出缓存淘汰策略,但从测试来看,目前使用的是LRU缓存淘汰策略。缓存的大小有限,当缓存被使用满时,我们需要清除掉部分数据,而清除哪些数据,由缓存淘汰策略决定。常见的算法有三种:首先,缓存的数据我们使用线性结构进行存储。为了满足最近最少使用的策略,我们需要维持线性表的数据按照使用时间排序。刚插入或者刚使用过的数据时间最近,放在线性表的

NSCache是苹果提供的内存缓存框架。它和NSMutableDictionary和用法很相似,但NSCache是线程安全的,并且没有对Key进行copy。虽然官方文档中没有明确指出缓存淘汰策略,但从测试来看,目前使用的是LRU缓存淘汰策略。

缓存淘汰策略

缓存的大小有限,当缓存被使用满时,我们需要清除掉部分数据,而清除哪些数据,由缓存淘汰策略决定。常见的算法有三种:

  • 先进先出策略FIFO(First In, First Out)
  • 最少使用策略LFU(Least Frequently Used)
  • 最近最少使用策略LRU(Least Recently Used)

LRU淘汰策略核心逻辑

首先,缓存的数据我们使用线性结构进行存储。为了满足最近最少使用的策略,我们需要维持线性表的数据按照使用时间排序。刚插入或者刚使用过的数据时间最近,放在线性表的最前头或者最后面。当缓存满清除数据时,我们将最近最少使用的数据清除出去,直到满足内存要求。

线性结构我们到底应该选数组还是链表呢?不管使用数组还是链表,当我们插入、查找和删除数据时,都需要先根据key查找目标对象,所以时间复杂度都为O(n)。 为了提高查询的效率,我们需要借助散列表(Hash table)来记录每个数据的位置。以空间换时间的方式,将查询的时间复杂度降低到O(1)。

现在我们除掉查找查找目标对象的情况下来分析一下使用数组和链表维持LRU的时间复杂度。

  • 数组实现(最近使用的放在最后)
    • 插入操作:如果插入元素不存在时,直接插入到数组尾部,时间复杂度为O(1),前提是在数组不需要扩容和清除数据的情况下,当容量处于内存满的临界点时,时间复杂度就蜕化为O(n);当插入元素存在时,需要先删除当前元素,再插入到尾部,时间复杂度为O(n)。
    • 查找操作:查找到元素后,需要先删除元素,在插入到尾部,时间复杂度为O(n)。
    • 删除操作:查找到目标元素删除时需要移动数据,时间复杂度为O(n)。
  • 链表实现
    • 插入操作:当插入元素不存在时,可以通过tail指针直接插入到尾部,时间复杂度为O(1);当插入元素存在时,需要先删除元素,再插入到尾部,删除的操作可以通过双向链表,将时间复杂度降为O(1)。
    • 查找操作:查找到元素后,需要先删除元素,在插入到尾部,同样使用双向链表和tail指针可以把删除和插入的操作时间复杂度降为O(1)。
    • 删除操作:使用双向链表将时间复杂度降为O(1)。

通过上面的分析可以,我们在更注重速度的情况下,我们更倾向于使用链表来维持LRU顺序。这样不管时插入、查询还是删除的操作,时间复杂度都为O(1)。

使用数组实现代码

JBCache.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface JBCache : NSObject

@property(nonatomic, copy) NSString *name;

@property(nonatomic, assign) NSUInteger totalCostLimit;

@property(nonatomic, assign) NSUInteger countLimit;

- (nullable id)objectForKey:(id)key;

- (void)setObject:(id)obj forKey:(id)key; // 0 cost

- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;

- (void)removeObjectForKey:(id)key;


@end

NS_ASSUME_NONNULL_END
复制代码

JBCache.m

#import "JBCache.h"

#ifndef JB_LOCK
#define JB_LOCK(semaphore) dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#endif

#ifndef JB_UNLOCK
#define JB_UNLOCK(semaphore) dispatch_semaphore_signal(semaphore);
#endif

@interface JBCacheItem : NSObject

@property(nonatomic, strong) id key;

@property(nonatomic, strong) id value;

@property(nonatomic, assign) NSUInteger cost;


@end

@implementation JBCacheItem



@end

@interface JBCache ()

@property(nonatomic, strong) NSMutableArray<JBCacheItem *> *lruCache;

@property(nonatomic, assign) NSUInteger currentTotalCost;

@property(nonatomic, strong) dispatch_semaphore_t semaphore;

@end
@implementation JBCache

- (instancetype)init {
    self = [super init];
    if (self) {
        self.lruCache = [[NSMutableArray alloc] init];
        
        self.semaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (nullable id)objectForKey:(id)key {//最后修改的放在最后(越近修改的数据放在越后面)
    
    JB_LOCK(self.semaphore)
    NSUInteger count = [self.lruCache count];
    
    id object;
    
    for (NSUInteger i = 0; i < count; i++) {
        JBCacheItem *cacheItem = self.lruCache[i];
        if ([cacheItem.key isEqual:key]) {
            
            if (i != (count - 1)) {
                [self.lruCache removeObjectAtIndex:i];
                [self.lruCache addObject:cacheItem];
            }
            object = cacheItem.value;
            break;
        }
    }
    JB_UNLOCK(self.semaphore)
    
    return object;
}

- (void)setObject:(id)obj forKey:(id)key {
    
    [self setObject:obj forKey:key cost:0];
}

- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {//在最后插入
    JB_LOCK(self.semaphore)
    
    if (self.totalCostLimit > 0 && self.totalCostLimit < g) {//totalCostLimit为0表示不限制
        return;
    }
    
    //需要先看缓存中是否已经存在
    NSUInteger count = [self.lruCache count];
    
    NSUInteger i = 0;
    for (; i < count; i++) {
        JBCacheItem *cacheItem = self.lruCache[i];
        if ([cacheItem.key isEqual:key]) {
            self.currentTotalCost -= cacheItem.cost;
            if(i != (count - 1)) {//已经在尾部,不需要换位置
                [self.lruCache removeObjectAtIndex:i];
                [self.lruCache addObject:cacheItem];
            }
            break;
        }
    }
    
    if (i == count) {//表示没有找到
        JBCacheItem *cacheItem = [[JBCacheItem alloc] init];
        cacheItem.key = key;
        cacheItem.value = obj;
        cacheItem.cost = g;
        [self.lruCache addObject:cacheItem];
        
        //看数量上是否超过数量
        if (self.countLimit > 0 && self.lruCache.count > self.countLimit) {//已经超过数量
            [self.lruCache removeObjectAtIndex:0];
            NSLog(@"超出数量,删除最后访问的数据");
        }
    }
    
    self.currentTotalCost += g;
    
    //看是否超出成本限制
    [self purgeDataByCostLimit];

    JB_UNLOCK(self.semaphore)
}

- (void)removeObjectForKey:(id)key {
    
    JB_LOCK(self.semaphore)
    
    NSUInteger count = [self.lruCache count];
    
    for (NSUInteger i = 0; i < count; i++) {
        JBCacheItem *cacheItem = self.lruCache[i];
        if ([cacheItem.key isEqual:key]) {
            [self.lruCache removeObjectAtIndex:i];
            self.currentTotalCost -= cacheItem.cost;
            break;
        }
    }
    
    JB_UNLOCK(self.semaphore)
}

- (void)setCountLimit:(NSUInteger)countLimit {
    
    JB_LOCK(self.semaphore)
    
    NSUInteger count = self.lruCache.count;
    if (countLimit > 0 && count > countLimit) {
        [self.lruCache removeObjectsInRange:NSMakeRange(0, count - countLimit)];
        NSLog(@"超出数量,删除最后访问的数据");
    }

    _countLimit = countLimit;
    
    JB_UNLOCK(self.semaphore)
}

- (void)setTotalCostLimit:(NSUInteger)totalCostLimit {
    
    JB_LOCK(self.semaphore)
    _totalCostLimit = totalCostLimit;
    [self purgeDataByCostLimit];
    JB_UNLOCK(self.semaphore)
}

- (void)purgeDataByCostLimit {
    
    if (_totalCostLimit > 0) {
        while (_currentTotalCost > _totalCostLimit) {
            _currentTotalCost -= self.lruCache[0].cost;
            [self.lruCache removeObjectAtIndex:0];
            NSLog(@"超出成本,删除最后访问的数据");
        }
    }
}


@end

复制代码

使用散列表+链表实现代码

JBLRUCache.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface JBLRUCache : NSObject

@property(nonatomic, copy) NSString *name;

@property(nonatomic, assign) NSUInteger totalCostLimit;

@property(nonatomic, assign) NSUInteger countLimit;

- (nullable id)objectForKey:(id)key;

- (void)setObject:(id)obj forKey:(id)key; // 0 cost

- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;

- (void)removeObjectForKey:(id)key;


@end

NS_ASSUME_NONNULL_END
复制代码

JBLRUCache.m

#import "JBLRUCache.h"

#ifndef JB_LOCK
#define JB_LOCK(semaphore) dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#endif

#ifndef JB_UNLOCK
#define JB_UNLOCK(semaphore) dispatch_semaphore_signal(semaphore);
#endif

@interface JBLRUCacheEntry : NSObject

@property(nonatomic, strong) id key;

@property(nonatomic, strong) id value;

@property(nonatomic, assign) NSUInteger cost;

@property(nonatomic, strong) JBLRUCacheEntry *preEntry;

@property(nonatomic, strong) JBLRUCacheEntry *nextEntry;

@end

@implementation JBLRUCacheEntry

@end

@interface JBLRUCache ()

@property(nonatomic, strong) JBLRUCacheEntry *head;

@property(nonatomic, strong) JBLRUCacheEntry *tail;

@property(nonatomic, strong) NSMapTable<id, JBLRUCacheEntry *> *entries;

@property(nonatomic, assign) NSUInteger currentTotalCost;

@property(nonatomic, strong) dispatch_semaphore_t semaphore;

@end

@implementation JBLRUCache

- (instancetype)init {
    self = [super init];
    if (self) {
        self.entries = [NSMapTable strongToStrongObjectsMapTable];
        self.semaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (nullable id)objectForKey:(id)key {
    
    JBLRUCacheEntry *entry;
    
    JB_LOCK(self.semaphore)
    entry = [self.entries objectForKey:key];
    if (entry && entry.nextEntry) {//表示存在,且不在链表的尾部,需要更换位置(如果在链表的尾部,那么nextEntry为nil)
        //删除节点
        [self removeEntry:entry];
        //插入节点(尾部)
        [self insertEntry:entry];
    }
    
    JB_UNLOCK(self.semaphore)
    
    return entry.value;
}

- (void)setObject:(id)obj forKey:(id)key {
    [self setObject:obj forKey:key cost:0];
}

- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    JB_LOCK(self.semaphore)
    
    //如果当前元素的成本已经大于成本限制,直接返回
    if (self.totalCostLimit > 0 && self.totalCostLimit < g) {
        return;
    }
    
    //先判断当前元素是否已经存在
    JBLRUCacheEntry *oldEntry = [self.entries objectForKey:key];
    if (oldEntry) {//已经存在,数量不会发生改变
        self.currentTotalCost -= oldEntry.cost;//删除旧的成本
        oldEntry.cost = g;
        oldEntry.value = obj;
        
        if(oldEntry.nextEntry) {//表示不在链表的尾部,需要交换位置
            //删除节点
            [self removeEntry:oldEntry];
            //插入节点(尾部)
            [self insertEntry:oldEntry];
        }
        
    } else {//不存在,数量会增加1
        JBLRUCacheEntry *entry = [[JBLRUCacheEntry alloc] init];
        entry.key = key;
        entry.value = obj;
        entry.cost = g;
        //插入节点(尾部)
        [self insertEntry:entry];
        [self.entries setObject:entry forKey:key];
    }
    
    self.currentTotalCost += g;//增加成本
    
    //先加入元素再清除溢出数据,会产生短暂的溢出情况。
    //最好是先清除再添加数据
    [self purgeDataByLimit];
    
    JB_UNLOCK(self.semaphore)
}

- (void)removeObjectForKey:(id)key {
    
    JB_LOCK(self.semaphore)
    
    //查找删除的元素是否存在
    JBLRUCacheEntry *entry = [self.entries objectForKey:key];
    if (entry) {//存在
        self.totalCostLimit -= entry.cost;
        [self removeEntry:entry];
    }
    
    JB_UNLOCK(self.semaphore)
}

- (void)setCountLimit:(NSUInteger)countLimit {
    
    JB_LOCK(self.semaphore)
    
    _countLimit = countLimit;
    [self purgeDataByLimit];
    
    JB_UNLOCK(self.semaphore)
}

- (void)setTotalCostLimit:(NSUInteger)totalCostLimit {
    JB_LOCK(self.semaphore)
    
    _totalCostLimit = totalCostLimit;
    [self purgeDataByLimit];
    
    JB_UNLOCK(self.semaphore)
}

- (void)removeEntry:(JBLRUCacheEntry *)entry {
    
    entry.preEntry.nextEntry = entry.nextEntry;
    entry.nextEntry.preEntry = entry.preEntry;
    
    if (entry == self.head) {
        self.head = entry.nextEntry;
    }
    if (entry == self.tail) {
        self.tail = entry.preEntry;
    }
    
    entry.preEntry = nil;
    entry.nextEntry = nil;
    
}

- (void)insertEntry:(JBLRUCacheEntry *)entry {//插入到尾部
    
    if(self.head) {
        self.tail.nextEntry = entry;
        entry.preEntry = self.tail;
        self.tail = entry;
    } else {//当前链表为空链表
        self.head = entry;
        self.tail = entry;
    }
}

- (void)purgeDataByLimit {
    
    if (_totalCostLimit > 0) {//成本限制
        while (_currentTotalCost > _totalCostLimit) {
            NSLog(@"超出成本,删除最后访问的数据");
            [self removeHeadEntry];
        }
    }
    
    if (_countLimit > 0) {//数量限制
        while (self.entries.count > _countLimit) {
            NSLog(@"超出数量,删除最后访问的数据");
            [self removeHeadEntry];
        }
    }
    
}

- (void)removeHeadEntry {//删除头节点
    JBLRUCacheEntry *entry = self.head;
    self.currentTotalCost -= entry.cost;
    [self removeEntry:entry];
    [self.entries removeObjectForKey:entry.key];
}

@end
复制代码

当然,上面的实现没有加入NSDiscardableContent对象的处理,后面改进过的gnustep的实现会提到。

苹果开源实现

网址: github.com/apple/swift…

但是缓存淘汰策略有点奇怪:优先按照cost升序排列,cost最小的放在表头;cost相同的,根据插入的时间升级排列,时间越短的放在越前面。清除数据是从表头开始的,也就是说cost越小、越后面插入的元素优先被删除。

个人觉得逻辑应该写反了,应该是:优先按照cost降序排列,cost最大大放在表头;cost相同大,根据插入时间的降序排列,时间越短的放在越后面。清除数据从表头开始。也就是说cost越大,优先被删除,cost相同的,按照LRU删除。

修改后的代码如下:

import Foundation

private class JBCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
    var key: KeyType //键
    var value: ObjectType //值
    var cost: Int //成本
    var prevByCost: JBCacheEntry? //上一个记录
    var nextByCost: JBCacheEntry? //下一个记录
    init(key: KeyType, value: ObjectType, cost: Int) {
        self.key = key
        self.value = value
        self.cost = cost
    }
}

fileprivate class JBCacheKey: NSObject {
    
    var value: AnyObject
    
    init(_ value: AnyObject) {
        self.value = value
        super.init()
    }
    
    override var hash: Int {
        switch self.value {
        case let nsObject as NSObject:
            return nsObject.hashValue
        case let hashable as AnyHashable:
            return hashable.hashValue
        default: return 0
        }
    }
    
    override func isEqual(_ object: Any?) -> Bool {
        guard let other = (object as? JBCacheKey) else { return false }
        
        if self.value === other.value {
            return true
        } else {
            guard let left = self.value as? NSObject,
                let right = other.value as? NSObject else { return false }
            
            return left.isEqual(right)
        }
    }
}

open class JBCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
    
    private var _entries = Dictionary<JBCacheKey, JBCacheEntry<KeyType, ObjectType>>()
    private let _lock = NSLock()
    private var _totalCost = 0
    private var _head: JBCacheEntry<KeyType, ObjectType>?
    
    open var name: String = ""
    open var totalCostLimit: Int = 0 // limits are imprecise/not strict
    open var countLimit: Int = 0 // limits are imprecise/not strict
    open var evictsObjectsWithDiscardedContent: Bool = false
    
    public override init() {}
    
    open weak var delegate: JBCacheDelegate?
    
    
    /// 根据键获取数据:不改变顺序
    ///
    /// - Parameter key: 键
    /// - Returns: 值
    open func object(forKey key: KeyType) -> ObjectType? {
        var object: ObjectType?
        
        let key = JBCacheKey(key)
        
        _lock.lock()
        if let entry = _entries[key] {
            object = entry.value
        }
        _lock.unlock()
        
        return object
    }
    
    open func setObject(_ obj: ObjectType, forKey key: KeyType) {
        setObject(obj, forKey: key, cost: 0)
    }
    
    private func remove(_ entry: JBCacheEntry<KeyType, ObjectType>) {//删除
        let oldPrev = entry.prevByCost
        let oldNext = entry.nextByCost
        
        oldPrev?.nextByCost = oldNext
        oldNext?.prevByCost = oldPrev
        
        if entry === _head {//如果删除为头节点,需要修改head的指向
            _head = oldNext
        }
    }
    
    private func insert(_ entry: JBCacheEntry<KeyType, ObjectType>) {//插入是怎么 排序 的
        guard var currentElement = _head else {//如果当前还没有元素
            // The cache is empty
            entry.prevByCost = nil //清空前后指针
            entry.nextByCost = nil
            
            _head = entry //当前插入的就是头节点
            return
        }
        //不是空节点, currentElement:为头节点
        //按照成本排序升序排列,cost最小的放在表头
        //成本相同的:根据插入的时间升级排序,时间越短的放在越前面
        //应该entry.cost <= currentElement.cost
        //成本大的放在前面,优先删除,成本相同的,按照LRU排序
       // guard entry.cost > currentElement.cost else {//如果要插入的节点的成本小于等于头节点,插入表头
        guard entry.cost <= currentElement.cost else {//如果要插入的节点的成本小于等于头节点,插入表头
            // Insert entry at the head
            entry.prevByCost = nil
            entry.nextByCost = currentElement
            currentElement.prevByCost = entry
            
            _head = entry
            return
        }
        
        //17
        //6 9 10()19 20 100
        
        //nextByCost.cost >= entry.cost
        //20
        //100 20 19 10 9 6
        //while let nextByCost = currentElement.nextByCost, nextByCost.cost < entry.cost {
        while let nextByCost = currentElement.nextByCost, nextByCost.cost >= entry.cost {
            currentElement = nextByCost
        }
        
        // Insert entry between currentElement and nextElement
        let nextElement = currentElement.nextByCost
        
        currentElement.nextByCost = entry
        entry.prevByCost = currentElement
        
        entry.nextByCost = nextElement
        nextElement?.prevByCost = entry
    }
    
    /// 设置数据
    ///
    /// - Parameters:
    ///   - obj: 数据
    ///   - key: 键
    ///   - g: 成本
    open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
        let g = max(g, 0)//数值校验
        let keyRef = JBCacheKey(key)
        
        _lock.lock() //需要加锁,保证线程安全(用的是互斥锁)
        
        let costDiff: Int //相差成本
        
        if let entry = _entries[keyRef] {//此键存在
            costDiff = g - entry.cost
            entry.cost = g //修改旧值的成本
            
            entry.value = obj //修改值
            
            //也就是说修改值,不改变位置
            if costDiff != 0 {//成本不一样
                remove(entry)//先删除,再插入
                insert(entry)//按照成本从小到大排序
            }
        } else {//数据不存在
            let entry = JBCacheEntry(key: key, value: obj, cost: g)
            _entries[keyRef] = entry //插入到索引表中
            insert(entry)
            
            costDiff = g
        }
        
        _totalCost += costDiff //增加的成本
        
        //purge [pɜːdʒ] 清除
        var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0 //清除量
        while purgeAmount > 0 { //成本的控制
            if let entry = _head {//从头开始删除
                delegate?.cache(unsafeDowncast(self, to:JBCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeAmount -= entry.cost
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[JBCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
        while purgeCount > 0 { //数量的控制
            if let entry = _head {//从头开始删除
                delegate?.cache(unsafeDowncast(self, to:JBCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
                
                _totalCost -= entry.cost
                purgeCount -= 1
                
                remove(entry) // _head will be changed to next entry in remove(_:)
                _entries[JBCacheKey(entry.key)] = nil
            } else {
                break
            }
        }
        
        _lock.unlock()
    }
    
    open func removeObject(forKey key: KeyType) {
        let keyRef = JBCacheKey(key)
        
        _lock.lock()
        if let entry = _entries.removeValue(forKey: keyRef) {
            _totalCost -= entry.cost
            remove(entry)
        }
        _lock.unlock()
    }
    
    open func removeAllObjects() {
        _lock.lock()
        _entries.removeAll()
        
        while let currentElement = _head {
            let nextElement = currentElement.nextByCost
            
            currentElement.prevByCost = nil
            currentElement.nextByCost = nil
            
            _head = nextElement
        }
        
        _totalCost = 0
        _lock.unlock()
    }
}

public protocol JBCacheDelegate : NSObjectProtocol {
    func cache(_ cache: JBCache<AnyObject, AnyObject>, willEvictObject obj: Any)
}

extension JBCacheDelegate {
    func cache(_ cache: JBCache<AnyObject, AnyObject>, willEvictObject obj: Any) {
        // Default implementation does nothing
    }
}
复制代码

gnustep开源实现

看源码时发现源码有些问题,没有加锁,同时对cost的计算也有些问题。缓存淘汰策略是针对可丢弃对象,只有可丢弃对象才有可能会被删除,淘汰的策略是LFU+LRU。即先根据最近不频繁使用筛选(使用次数小于平均使用次数的5分之1 + 1),然后再根据LRU进行删除。 修改后的代码如下: GNUCache.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN


@class GNUCache;

/**
 * Protocol implemented by NSCache delegate objects.
 */
@protocol GNUCacheDelegate
/**
 * Delegate method, called just before the cache removes an object, either as
 * the result of user action or due to the cache becoming full.
 */
- (void) cache: (GNUCache*)cache willEvictObject: (id)obj;
@end



@interface GNUCache<KeyT, ValT> : NSObject 

/** Name of this cache. */
@property(nonatomic, copy) NSString *name;
/**
 The maximum total cost of all cache objects.
 This limit is advisory; caches may choose to disregard it temporarily or permanently.
 A limit of 0 is used to indicate no limit; this is the default.
 */
@property(nonatomic, assign) NSUInteger totalCostLimit;

/** Total cost of currently-stored objects. */
@property(nonatomic,readonly, assign) NSUInteger totalCost;

/**
 The maximum number of objects in the cache.
 This limit is advisory; caches may choose to disregard it temporarily or permanently.
 A limit of 0 is used to indicate no limit; this is the default.
 */
@property(nonatomic, assign) NSUInteger countLimit;

/** The delegate object, notified when objects are about to be evicted. */
@property(nonatomic, weak) id<GNUCacheDelegate> delegate;

/**
 Flag indicating whether discarded objects should be evicted
 
 Sets whether this cache will evict(清除) objects that conform to the
 NSDiscardableContent protocol, or simply discard their contents.
 
 Returns whether objects stored in this cache which implement the
 NSDiscardableContent protocol are removed from the cache when their contents are evicted.
 
 */
@property(nonatomic, assign) BOOL evictsObjectsWithDiscardedContent;

/**
 * Returns an object associated with the specified key in this cache.
 */
- (ValT) objectForKey:(KeyT) key;

/**
 * Removes all objects from this cache.
 */
- (void) removeAllObjects;

/**
 * Removes the object associated with the given key.
 */
- (void) removeObjectForKey: (KeyT) key;

/**
 * Adds an object and its associated cost.  The cache will endeavor to keep the
 * total cost below the value set with -setTotalCostLimit: by discarding the
 * contents of objects which implement the NSDiscardableContent protocol.
 */
- (void) setObject: (ValT) obj forKey: (KeyT) key cost: (NSUInteger)num;

/**
 * Adds an object to the cache without associating a cost with it.
 */
- (void) setObject: (ValT) obj forKey: (KeyT) key;

@end

NS_ASSUME_NONNULL_END
复制代码

GNUCache.m

#import "GNUCache.h"

#ifndef GNU_LOCK
#define GNU_LOCK(semaphore) dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
#endif

#ifndef GNU_UNLOCK
#define GNU_UNLOCK(semaphore) dispatch_semaphore_signal(semaphore);
#endif

/**
 * _GSCachedObject is effectively used as a structure containing the various
 * things that need to be associated with objects stored in an NSCache.  It is
 * an NSObject subclass so that it can be used with OpenStep collection
 * classes.
 */
@interface _GSCachedObject : NSObject

@property(nonatomic, strong) id object;

@property(nonatomic, strong) id key;

@property(nonatomic, assign) int accessCount;

@property(nonatomic, assign) NSUInteger cost;

@property(nonatomic, assign) BOOL isEvictable;//是否可回收

@end

@implementation _GSCachedObject
@end


@interface GNUCache<KeyT, ValT> ()

/** Total cost of currently-stored objects. */
@property(nonatomic,assign) NSUInteger totalCost;

/** The mapping from names to objects in this cache. */
@property(nonatomic, strong) NSMapTable *objects;

//只清除可回收对象
/** LRU ordering of all potentially-evictable objects in this cache. */
@property(nonatomic, strong)NSMutableArray<ValT> *accesses; //存储潜在的可回收对象

/** Total number of accesses to objects */
@property(nonatomic, assign) int64_t totalAccesses;//总访问次数

@property(nonatomic, strong) dispatch_semaphore_t semaphore;

/** The method controlling eviction policy in an NSCache. */
- (void) _evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost;

@end

@implementation GNUCache

- (instancetype)init {
    self = [super init];

    if (self) {
        self.objects = [NSMapTable strongToStrongObjectsMapTable];
        self.accesses = [[NSMutableArray alloc] init];
        self.semaphore = dispatch_semaphore_create(1);
    }
    return self;
}


- (id) objectForKey: (id)key {//获取数据
    
    GNU_LOCK(self.semaphore)
    
    _GSCachedObject *obj = [self.objects objectForKey: key];
    
    if (obj) {
        
        if (obj.isEvictable) {//是否可回收
            //对可回收对象维持LRU排序,把刚访问的对象放置最后
            // Move the object to the end of the access list.
            [self.accesses removeObjectIdenticalTo: obj];//先删除指定元素
            [self.accesses addObject: obj];//将数据加载到末尾
        }
        
        //改变访问次数
        obj.accessCount++;
        self.totalAccesses++;
    }
    
    GNU_UNLOCK(self.semaphore)
    
    return obj.object;
}

- (void) removeAllObjects {//清除所有对象
    
    GNU_LOCK(self.semaphore)
    
    NSEnumerator *e = [self.objects objectEnumerator];
    _GSCachedObject *obj;
    
    while (nil != (obj = [e nextObject])) {//将要被回收
        [_delegate cache: self willEvictObject: obj.object];
    }
    
    [self.objects removeAllObjects];
    [self.accesses removeAllObjects];
    self.totalAccesses = 0;
    
    self.totalCost = 0;//应该清除成本
    
    GNU_UNLOCK(self.semaphore)
}

/**
 根据键删除对象,没有看到成本减少的代码?????
 时间复杂度:O(n) 因为需要遍历LRU数组找到指定对象
 
 @param key 键
 */
- (void) removeObjectForKey: (id)key {//清除对象
    GNU_LOCK(self.semaphore)
    
    [self _removeObjectForKey:key];
    
    GNU_UNLOCK(self.semaphore)
}

- (void)_removeObjectForKey: (id)key {//清除对象
    _GSCachedObject *obj = [self.objects objectForKey: key];//找到对象
    
    if (nil != obj) {
        
        [_delegate cache: self willEvictObject: obj.object];//代理
        self.totalAccesses -= obj.accessCount;//访问次数
        self.totalCost -= obj.cost;//应该减去成本
        [self.objects removeObjectForKey: key];
        [self.accesses removeObjectIdenticalTo: obj];//需要遍历数组
    }
}

/**
 添加对象
 
 @param obj 对象
 @param key 键
 @param num 成本
 */
- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num {
    
    GNU_LOCK(self.semaphore)
    
    _GSCachedObject *object = [self.objects objectForKey: key];
    _GSCachedObject *newObject;
    
    if (nil != object) {//存在先删除
        [self _removeObjectForKey: object.key];//没有看到减少当前对象的成本(已经加上)
    }
    
    //计算成本evict:[ɪˈvɪkt]驱逐,赶出,逐出
    //先看是否会超出成本加上当前元素的成本,如果超过,先清除(先清除后加入能保证不会超过)
    //如果先添加再判断的话,会出现大于的情况
    [self _evictObjectsToMakeSpaceForObjectWithCost: num];
    
    newObject = [[_GSCachedObject alloc] init]; //创建新对象
    
    newObject.object = obj;
    newObject.key = key;
    newObject.cost = num;
    
    if ([obj conformsToProtocol: @protocol(NSDiscardableContent)]) {//可废弃的
        newObject.isEvictable = YES;
        [self.accesses addObject: newObject];//将可回收对象(NSDiscardableContent)加到_accesses中
    }
    
    [self.objects setObject: newObject forKey: key];//增加到字典中(目的就是使查找到时间复杂度变为O(1))
    _totalCost += num;//增加成本
    
    GNU_UNLOCK(self.semaphore)
}

- (void) setObject: (id)obj forKey: (id)key {
    [self setObject: obj forKey: key cost: 0];
}

/**
 * This method is the one that handles the eviction policy.  This
 * implementation uses a relatively simple LRU/LFU hybrid.  The NSCache
 * documentation from Apple makes it clear that the policy may change, so we
 * could in future have a class cluster with pluggable policies for different
 * caches or some other mechanism.
 */
- (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost {
    
    NSUInteger spaceNeeded = 0;//超出的成本,也就是需要清除的成本
    NSUInteger count = [self.objects count];//总数量
    
    //成本限制 > 0:表示需要考虑成本限制; 成本超出了限制
    if (_totalCostLimit > 0 && _totalCost + cost > _totalCostLimit) {
        spaceNeeded = _totalCost + cost - _totalCostLimit;
    }
    
    
    // Only evict if we need the space.
    //有数据 而且 (成本或者数量超出限制)数量超出只会清除一次,因为add最多多1个记录
    if (count > 0 && (spaceNeeded > 0 || count >= _countLimit)) {
        
        //只会清除可清除对象
        NSMutableArray *evictedKeys = nil;//存储可清除对象
        
        // Round up slightly.(轻微向上取整)
        //临界访问次数 = (平均访问次数 * 0.2)+ 1
        NSUInteger averageAccesses = ((self.totalAccesses / (double)count) * 0.2) + 1;
        
        NSEnumerator *e = [self.accesses objectEnumerator];//遍历可丢弃对象
        _GSCachedObject *obj;
        
        if (_evictsObjectsWithDiscardedContent) {//清除可丢弃对象
            evictedKeys = [[NSMutableArray alloc] init];
        }
        
        while (nil != (obj = [e nextObject])) {//遍历
            // Don't evict frequently accessed objects.
            //不清除频繁访问的对象, 访问次数少于临界访问次数的清除 (同时必须为可清除对象,这里保存的都是可清除对象)
            if (obj.accessCount < averageAccesses && obj.isEvictable) {
                
                [obj.object discardContentIfPossible]; //丢弃对象
                
                if ([obj.object isContentDiscarded]) {//丢弃成功
                    
                     NSLog(@"---------丢弃成功---------");
                    
                    NSUInteger _cost = obj.cost;//当前对象的成本
                    
                    // Evicted objects have no cost.
                    obj.cost = 0;
                    // Don't try evicting this again in future; it's gone already.
                    obj.isEvictable = NO;
                    // Remove this object as well as its contents if required
                    if (_evictsObjectsWithDiscardedContent) {
                        [evictedKeys addObject: obj.key];
                    }
                    _totalCost -= _cost;//减少总成本
                    // If we've freed enough space, give up
                    if (_cost > spaceNeeded) {//已经够了,退出
                        break;
                    }
                    spaceNeeded -= _cost;//减少需要清除的成本
                }
            }
        }
        // Evict all of the objects whose content we have discarded if required
        if (_evictsObjectsWithDiscardedContent) {
            NSString *key;
            
            e = [evictedKeys objectEnumerator];
            while (nil != (key = [e nextObject])) {//清除数据
                [self _removeObjectForKey: key];
            }
        }
    }
}

@end
复制代码

测试核心代码

self.cache = [[GNUCache alloc] init];
    self.cache.name = @"image_cache";
    self.cache.countLimit = 10;//最大缓存数目
    self.cache.totalCostLimit = 100 * 1024 * 1024;
    self.cache.evictsObjectsWithDiscardedContent = YES;
    self.cache.delegate = self;
    
    for (NSString *imageUrl in self.imageUrls) {

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            NSPurgeableData *purgeableData = [self.cache objectForKey:imageUrl];

            if (purgeableData == nil || [purgeableData isContentDiscarded]) {
                NSLog(@"从网络中加载图片");
                NSURL *url = [NSURL URLWithString:imageUrl];
                NSData *data = [NSData dataWithContentsOfURL:url];
                //创建好NSPurgeableData对象之后,其”purge引用计数“会多1,所以无须再调用beginContentAccess了
                purgeableData = [NSPurgeableData dataWithData:data];
                [self.cache setObject:purgeableData forKey:imageUrl cost:[purgeableData length]];
            } else {
                NSLog(@"从内存缓存中加载图片");
                [purgeableData beginContentAccess];
            }

            UIImage *image;
            if (purgeableData) {
                image = [UIImage imageWithWebPData:purgeableData];
                [purgeableData endContentAccess];

            } else {
                image = [UIImage imageNamed:@"200empty_list"];
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = image;
            });

        });
    }

复制代码

TMCache中的TMMemoryCache

TMMemoryCache由 Tumblr 开发,但现在已经不再维护了。特点:

  • 提供完善的缓存清除策略,包括:数量限制、总容量限制、存活时间限制、内存警告或应用退到后台时清空缓存等。
  • 线程安全的。利用GCD的栅栏技术实现了多读单写方案。在并发读取的场景下能提高读取性能。但因为使用了异步的方式,线程的频繁切换反而大大消耗了性能,而且层级间的异步调用稍微不注意就容易产生死锁和内存泄漏。个人觉得内存缓存没有必要做异步接口,如果仅为了实现多读单写,那直接使用pthread_rwlock(读写锁)即可。

代码如下,含部分修改

TMMemoryCache.h

/**
 `TMMemoryCache` is a fast, thread safe key/value store similar to `NSCache`. On iOS it will clear itself
 automatically to reduce memory usage when the app receives a memory warning or goes into the background.

 Access is natively asynchronous. Every method accepts a callback block that runs on a concurrent
 <queue>, with cache writes protected by GCD barriers. Synchronous variations are provided.
 
 All access to the cache is dated so the that the least-used objects can be trimmed first. Setting an
 optional <ageLimit> will trigger a GCD timer to periodically to trim the cache to that age.
 
 Objects can optionally be set with a "cost", which could be a byte count or any other meaningful integer.
 Setting a <costLimit> will automatically keep the cache below that value with <trimToCostByDate:>.

 Values will not persist after application relaunch or returning from the background. See <TMCache> for
 a memory cache backed by a disk cache.
 */

#import <Foundation/Foundation.h>

@class TMMemoryCache;

typedef void (^TMMemoryCacheBlock)(TMMemoryCache *cache);
typedef void (^TMMemoryCacheObjectBlock)(TMMemoryCache *cache, NSString *key, id object);

@interface TMMemoryCache : NSObject

#pragma mark -
/// @name Core

/**
 A concurrent queue on which all work is done. It is exposed here so that it can be set to target some
 other queue, such as a global concurrent queue with a priority other than the default.
 */
@property (readonly) dispatch_queue_t queue;

/**
 The total accumulated cost.
 */
@property (readonly) NSUInteger totalCost;

/**
 The maximum cost allowed to accumulate before objects begin to be removed with <trimToCostByDate:>.
 */
@property (assign) NSUInteger costLimit;

/**
 The maximum number of seconds an object is allowed to exist in the cache. Setting this to a value
 greater than `0.0` will start a recurring GCD timer with the same period that calls <trimToDate:>.
 Setting it back to `0.0` will stop the timer. Defaults to `0.0`.
 */
@property (assign) NSTimeInterval ageLimit;

/**
 When `YES` on iOS the cache will remove all objects when the app receives a memory warning.
 Defaults to `YES`.
 */
@property (assign) BOOL removeAllObjectsOnMemoryWarning;

/**
 When `YES` on iOS the cache will remove all objects when the app enters the background.
 Defaults to `YES`.
 */
@property (assign) BOOL removeAllObjectsOnEnteringBackground;

#pragma mark -
/// @name Event Blocks

/**
 A block to be executed just before an object is added to the cache. This block will be excuted within
 a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheObjectBlock willAddObjectBlock;

/**
 A block to be executed just before an object is removed from the cache. This block will be excuted
 within a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheObjectBlock willRemoveObjectBlock;

/**
 A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>.
 This block will be excuted within a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheBlock willRemoveAllObjectsBlock;

/**
 A block to be executed just after an object is added to the cache. This block will be excuted within
 a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheObjectBlock didAddObjectBlock;

/**
 A block to be executed just after an object is removed from the cache. This block will be excuted
 within a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheObjectBlock didRemoveObjectBlock;

/**
 A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>.
 This block will be excuted within a barrier, i.e. all reads and writes are suspended for the duration of the block.
 */
@property (copy) TMMemoryCacheBlock didRemoveAllObjectsBlock;

/**
 A block to be executed upon receiving a memory warning (iOS only) potentially in parallel with other blocks on the <queue>.
 This block will be executed regardless of the value of <removeAllObjectsOnMemoryWarning>. Defaults to `nil`.
 */
@property (copy) TMMemoryCacheBlock didReceiveMemoryWarningBlock;

/**
 A block to be executed when the app enters the background (iOS only) potentially in parallel with other blocks on the <queue>.
 This block will be executed regardless of the value of <removeAllObjectsOnEnteringBackground>. Defaults to `nil`.
 */
@property (copy) TMMemoryCacheBlock didEnterBackgroundBlock;

#pragma mark -
/// @name Shared Cache

/**
 A shared cache.
 
 @result The shared singleton cache instance.
 */
+ (instancetype)sharedCache;

#pragma mark -
/// @name Asynchronous Methods

/**
 Retrieves the object for the specified key. This method returns immediately and executes the passed
 block after the object is available, potentially in parallel with other blocks on the <queue>.
 
 @param key The key associated with the requested object.
 @param block A block to be executed concurrently when the object is available.
 */
- (void)objectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;

/**
 Stores an object in the cache for the specified key. This method returns immediately and executes the
 passed block after the object has been stored, potentially in parallel with other blocks on the <queue>.
 
 @param object An object to store in the cache.
 @param key A key to associate with the object. This string will be copied.
 @param block A block to be executed concurrently after the object has been stored, or nil.
 */
- (void)setObject:(id)object forKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;

/**
 Stores an object in the cache for the specified key and the specified cost. If the cost causes the total
 to go over the <costLimit> the cache is trimmed (oldest objects first). This method returns immediately
 and executes the passed block after the object has been stored, potentially in parallel with other blocks
 on the <queue>.
 
 @param object An object to store in the cache.
 @param key A key to associate with the object. This string will be copied.
 @param cost An amount to add to the <totalCost>.
 @param block A block to be executed concurrently after the object has been stored, or nil.
 */
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(TMMemoryCacheObjectBlock)block;

/**
 Removes the object for the specified key. This method returns immediately and executes the passed
 block after the object has been removed, potentially in parallel with other blocks on the <queue>.
 
 @param key The key associated with the object to be removed.
 @param block A block to be executed concurrently after the object has been removed, or nil.
 */
- (void)removeObjectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block;

/**
 Removes all objects from the cache that have not been used since the specified date.
 This method returns immediately and executes the passed block after the cache has been trimmed,
 potentially in parallel with other blocks on the <queue>.
 
 @param date Objects that haven't been accessed since this date are removed from the cache.
 @param block A block to be executed concurrently after the cache has been trimmed, or nil.
 */
- (void)trimToDate:(NSDate *)date block:(TMMemoryCacheBlock)block;

/**
 Removes objects from the cache, costliest objects first, until the <totalCost> is below the specified
 value. This method returns immediately and executes the passed block after the cache has been trimmed,
 potentially in parallel with other blocks on the <queue>.
 
 @param cost The total accumulation allowed to remain after the cache has been trimmed.
 @param block A block to be executed concurrently after the cache has been trimmed, or nil.
 */
- (void)trimToCost:(NSUInteger)cost block:(TMMemoryCacheBlock)block;

/**
 Removes objects from the cache, ordered by date (least recently used first), until the <totalCost> is below
 the specified value. This method returns immediately and executes the passed block after the cache has been
 trimmed, potentially in parallel with other blocks on the <queue>.

 @param cost The total accumulation allowed to remain after the cache has been trimmed.
 @param block A block to be executed concurrently after the cache has been trimmed, or nil.
 */
- (void)trimToCostByDate:(NSUInteger)cost block:(TMMemoryCacheBlock)block;

/**
 Removes all objects from the cache. This method returns immediately and executes the passed block after
 the cache has been cleared, potentially in parallel with other blocks on the <queue>.
 
 @param block A block to be executed concurrently after the cache has been cleared, or nil.
 */
- (void)removeAllObjects:(TMMemoryCacheBlock)block;

/**
 Loops through all objects in the cache within a memory barrier (reads and writes are suspended during the enumeration).
 This method returns immediately.

 @param block A block to be executed for every object in the cache.
 @param completionBlock An optional block to be executed concurrently when the enumeration is complete.
 */
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block completionBlock:(TMMemoryCacheBlock)completionBlock;

#pragma mark -
/// @name Synchronous Methods

/**
 Retrieves the object for the specified key. This method blocks the calling thread until the
 object is available.
 
 @see objectForKey:block:
 @param key The key associated with the object.
 @result The object for the specified key.
 */
- (id)objectForKey:(NSString *)key;

/**
 Stores an object in the cache for the specified key. This method blocks the calling thread until the object
 has been set.

 @see setObject:forKey:block:
 @param object An object to store in the cache.
 @param key A key to associate with the object. This string will be copied.
 */
- (void)setObject:(id)object forKey:(NSString *)key;

/**
 Stores an object in the cache for the specified key and the specified cost. If the cost causes the total
 to go over the <costLimit> the cache is trimmed (oldest objects first). This method blocks the calling thread
 until the object has been stored.

 @param object An object to store in the cache.
 @param key A key to associate with the object. This string will be copied.
 @param cost An amount to add to the <totalCost>.
 */
- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost;

/**
 Removes the object for the specified key. This method blocks the calling thread until the object
 has been removed.
 
 @param key The key associated with the object to be removed.
 */
- (void)removeObjectForKey:(NSString *)key;

/**
 Removes all objects from the cache that have not been used since the specified date.
 This method blocks the calling thread until the cache has been trimmed.
 
 @param date Objects that haven't been accessed since this date are removed from the cache.
 */
- (void)trimToDate:(NSDate *)date;

/**
 Removes objects from the cache, costliest objects first, until the <totalCost> is below the specified
 value. This method blocks the calling thread until the cache has been trimmed.
 
 @param cost The total accumulation allowed to remain after the cache has been trimmed.
 */
- (void)trimToCost:(NSUInteger)cost;

/**
 Removes objects from the cache, ordered by date (least recently used first), until the <totalCost> is below
 the specified value. This method blocks the calling thread until the cache has been trimmed.

 @param cost The total accumulation allowed to remain after the cache has been trimmed.
 */
- (void)trimToCostByDate:(NSUInteger)cost;

/**
 Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
 */
- (void)removeAllObjects;

/**
 Loops through all objects in the cache within a memory barrier (reads and writes are suspended during the enumeration).
 This method blocks the calling thread until all objects have been enumerated.

 @param block A block to be executed for every object in the cache.
 
 @warning Do not call this method within the event blocks (<didReceiveMemoryWarningBlock>, etc.)
 Instead use the asynchronous version, <enumerateObjectsWithBlock:completionBlock:>.
 
 */
- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block;

/**
 Handle a memory warning.
 */
- (void)handleMemoryWarning __deprecated_msg("This happens automatically in TMCache 2.1. There’s no longer a need to call it directly.");

/**
 Handle the application having been backgrounded.
 */
- (void)handleApplicationBackgrounding __deprecated_msg("This happens automatically in TMCache 2.1. There’s no longer a need to call it directly.");

@end

复制代码

TMMemoryCache.m

#import "TMMemoryCache.h"

#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
#import <UIKit/UIKit.h>
#endif

NSString * const TMMemoryCachePrefix = @"com.tumblr.TMMemoryCache";

@interface TMMemoryCache ()
#if OS_OBJECT_USE_OBJC
@property (strong, nonatomic) dispatch_queue_t queue;
#else
@property (assign, nonatomic) dispatch_queue_t queue;
#endif
@property (strong, nonatomic) NSMutableDictionary *dictionary;//存储数据
@property (strong, nonatomic) NSMutableDictionary *dates;//存储日期
@property (strong, nonatomic) NSMutableDictionary *costs;//存储消耗
@end

@implementation TMMemoryCache

@synthesize ageLimit = _ageLimit;
@synthesize costLimit = _costLimit;
@synthesize totalCost = _totalCost;
@synthesize willAddObjectBlock = _willAddObjectBlock;
@synthesize willRemoveObjectBlock = _willRemoveObjectBlock;
@synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
@synthesize didAddObjectBlock = _didAddObjectBlock;
@synthesize didRemoveObjectBlock = _didRemoveObjectBlock;
@synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
@synthesize didReceiveMemoryWarningBlock = _didReceiveMemoryWarningBlock;
@synthesize didEnterBackgroundBlock = _didEnterBackgroundBlock;

#pragma mark - Initialization -

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(_queue);
    _queue = nil;
    #endif
}

- (id)init
{
    if (self = [super init]) {
        NSString *queueName = [[NSString alloc] initWithFormat:@"%@.%p", TMMemoryCachePrefix, self];
        //并发队列
        _queue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);

        _dictionary = [[NSMutableDictionary alloc] init];
        _dates = [[NSMutableDictionary alloc] init];
        _costs = [[NSMutableDictionary alloc] init];

        _willAddObjectBlock = nil;
        _willRemoveObjectBlock = nil;
        _willRemoveAllObjectsBlock = nil;

        _didAddObjectBlock = nil;
        _didRemoveObjectBlock = nil;
        _didRemoveAllObjectsBlock = nil;

        _didReceiveMemoryWarningBlock = nil;
        _didEnterBackgroundBlock = nil;

        _ageLimit = 0.0;
        _costLimit = 0;
        _totalCost = 0;

        _removeAllObjectsOnMemoryWarning = YES;
        _removeAllObjectsOnEnteringBackground = YES;
        
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleMemoryWarning)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleApplicationBackgrounding)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }
    return self;
}

+ (instancetype)sharedCache
{
    static id cache;
    static dispatch_once_t predicate;

    dispatch_once(&predicate, ^{
        cache = [[self alloc] init];
    });

    return cache;
}

#pragma mark - Private Methods -

- (void)handleMemoryWarning //内存警告时清除缓存
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
    
    if (self.removeAllObjectsOnMemoryWarning)
        [self removeAllObjects:nil];
    
    __weak TMMemoryCache *weakSelf = self;
    
    dispatch_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;
        
        if (strongSelf->_didReceiveMemoryWarningBlock)
            strongSelf->_didReceiveMemoryWarningBlock(strongSelf);
    });
    
#endif
}

- (void)handleApplicationBackgrounding
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
    
    if (self.removeAllObjectsOnEnteringBackground) //进入后台时清除缓存
        [self removeAllObjects:nil];
    
    __weak TMMemoryCache *weakSelf = self;
    
    dispatch_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;
        
        if (strongSelf->_didEnterBackgroundBlock)
            strongSelf->_didEnterBackgroundBlock(strongSelf);
    });
    
#endif
}

- (void)removeObjectAndExecuteBlocksForKey:(NSString *)key
{
    id object = [_dictionary objectForKey:key];
    NSNumber *cost = [_costs objectForKey:key];

    if (_willRemoveObjectBlock)
        _willRemoveObjectBlock(self, key, object);

    if (cost)
        _totalCost -= [cost unsignedIntegerValue];

    [_dictionary removeObjectForKey:key];//移除数据
    [_dates removeObjectForKey:key];//移除日期
    [_costs removeObjectForKey:key];//移除消耗

    if (_didRemoveObjectBlock)
        _didRemoveObjectBlock(self, key, nil);
}

- (void)trimMemoryToDate:(NSDate *)trimDate //根据日期缩容
{
    NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
    
    for (NSString *key in keysSortedByDate) { // oldest objects first
        NSDate *accessDate = [_dates objectForKey:key];
        if (!accessDate)
            continue;
        
        if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date
            [self removeObjectAndExecuteBlocksForKey:key];
        } else {
            break;
        }
    }
}

/**
 根据成本大小优先缩容

 @param limit 容量限制
 */
- (void)trimToCostLimit:(NSUInteger)limit
{
    if (_totalCost <= limit)
        return;

    NSArray *keysSortedByCost = [_costs keysSortedByValueUsingSelector:@selector(compare:)];

    for (NSString *key in [keysSortedByCost reverseObjectEnumerator]) { // costliest objects first
        [self removeObjectAndExecuteBlocksForKey:key];

        if (_totalCost <= limit)
            break;
    }
}

/**
 根据LRU缩容

 @param limit 最大成本
 */
- (void)trimToCostLimitByDate:(NSUInteger)limit
{
    if (_totalCost <= limit)
        return;
    
    //根据key排序返回排序后的数组
    NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];

    for (NSString *key in keysSortedByDate) { // oldest objects first
        [self removeObjectAndExecuteBlocksForKey:key];

        if (_totalCost <= limit)
            break;
    }
}

/**
 根据年龄缩容,也就是设置最大存活周期
 递归执行(定时)
 这种写法有问题:就是多次调用,然后也不能取消
 */
- (void)trimToAgeLimitRecursively
{
    if (_ageLimit == 0.0)
        return;

    NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-_ageLimit];
    [self trimMemoryToDate:date];
    
    __weak TMMemoryCache *weakSelf = self;
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_ageLimit * NSEC_PER_SEC));
    dispatch_after(time, _queue, ^(void){
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;
        
        __weak TMMemoryCache *weakSelf = strongSelf;
        
        dispatch_barrier_async(strongSelf->_queue, ^{
            TMMemoryCache *strongSelf = weakSelf;
            [strongSelf trimToAgeLimitRecursively];
        });
    });
}

#pragma mark - Public Asynchronous Methods -
//异步方法
- (void)objectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
    if (!key || !block)
        return;
    
    NSDate *now = [[NSDate alloc] init];
    
    __weak TMMemoryCache *weakSelf = self;
    
    //线程的切换
    //多读单写,读取并发,写入同步
    //对于会经常产生并发读操作的场景下,可以提高读取性能。
    //如果缓存用于做图片缓存,意义就不是很大,因为经常情况下不会在同一页面显示两张地址相同的图片
    dispatch_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        
        if (!strongSelf)
            return;

        id object = [strongSelf->_dictionary objectForKey:key];

        if (object) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_barrier_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    [strongSelf->_dates setObject:now forKey:key];//设置访问时间
            });
        }

        block(strongSelf, key, object);
    });
}

- (void)setObject:(id)object forKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
    [self setObject:object forKey:key withCost:0 block:block];
}

- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(TMMemoryCacheObjectBlock)block
{
    NSDate *now = [[NSDate alloc] init];

    if (!key || !object)
        return;

    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        if (strongSelf->_willAddObjectBlock)
            strongSelf->_willAddObjectBlock(strongSelf, key, object);

        [strongSelf->_dictionary setObject:object forKey:key];//数据
        [strongSelf->_dates setObject:now forKey:key]; //日期
        
        //这种写法有问题,强应用了self,这个计算也有问题,如果key已经在缓存中存在时,应该先减去已经存在的cost
        //_totalCost += cost;
        //改为
        NSNumber *oldCost = [strongSelf->_costs objectForKey:key];
        if (oldCost) {
            strongSelf->_totalCost -= oldCost.unsignedIntegerValue;
        }
        strongSelf->_totalCost += cost;
 
        [strongSelf->_costs setObject:@(cost) forKey:key];//成本

        if (strongSelf->_didAddObjectBlock)
            strongSelf->_didAddObjectBlock(strongSelf, key, object);

        if (strongSelf->_costLimit > 0) //考虑成本限制,根据日期缩容:LRU
            [strongSelf trimToCostByDate:strongSelf->_costLimit block:nil];

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf, key, object);
            });
        }
    });
}

- (void)removeObjectForKey:(NSString *)key block:(TMMemoryCacheObjectBlock)block
{
    if (!key)
        return;

    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        [strongSelf removeObjectAndExecuteBlocksForKey:key];

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf, key, nil);
            });
        }
    });
}

- (void)trimToDate:(NSDate *)trimDate block:(TMMemoryCacheBlock)block
{
    if (!trimDate)
        return;

    if ([trimDate isEqualToDate:[NSDate distantPast]]) {
        [self removeAllObjects:block];
        return;
    }

    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        [strongSelf trimMemoryToDate:trimDate];

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf);
            });
        }
    });
}

/**
 缩容:缩容策略:成本高的优先去除

 @param cost 最大成本
 @param block 回调
 */
- (void)trimToCost:(NSUInteger)cost block:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        [strongSelf trimToCostLimit:cost];

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf);
            });
        }
    });
}

- (void)trimToCostByDate:(NSUInteger)cost block:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        [strongSelf trimToCostLimitByDate:cost];

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf);
            });
        }
    });
}

- (void)removeAllObjects:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        if (strongSelf->_willRemoveAllObjectsBlock)
            strongSelf->_willRemoveAllObjectsBlock(strongSelf);

        [strongSelf->_dictionary removeAllObjects];
        [strongSelf->_dates removeAllObjects];
        [strongSelf->_costs removeAllObjects];
        
        strongSelf->_totalCost = 0;

        if (strongSelf->_didRemoveAllObjectsBlock)
            strongSelf->_didRemoveAllObjectsBlock(strongSelf);

        if (block) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    block(strongSelf);
            });
        }
    });
}

- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block completionBlock:(TMMemoryCacheBlock)completionBlock
{
    if (!block)
        return;

    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        NSArray *keysSortedByDate = [strongSelf->_dates keysSortedByValueUsingSelector:@selector(compare:)];
        
        for (NSString *key in keysSortedByDate) {
            block(strongSelf, key, [strongSelf->_dictionary objectForKey:key]);
        }

        if (completionBlock) {
            __weak TMMemoryCache *weakSelf = strongSelf;
            dispatch_async(strongSelf->_queue, ^{
                TMMemoryCache *strongSelf = weakSelf;
                if (strongSelf)
                    completionBlock(strongSelf);
            });
        }
    });
}

#pragma mark - Public Synchronous Methods -

- (id)objectForKey:(NSString *)key
{
    if (!key)
        return nil;

    __block id objectForKey = nil;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self objectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) {
        objectForKey = object;
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    //If your app is built with a deployment target of macOS 10.8 and later or iOS v6.0 and later, dispatch queues are typically managed by ARC, so you do not need to retain or release the dispatch queues.
    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif

    return objectForKey;
}

- (void)setObject:(id)object forKey:(NSString *)key
{
    [self setObject:object forKey:key withCost:0];
}

- (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost
{
    if (!object || !key)
        return;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self setObject:object forKey:key withCost:cost block:^(TMMemoryCache *cache, NSString *key, id object) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)removeObjectForKey:(NSString *)key
{
    if (!key)
        return;
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self removeObjectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)trimToDate:(NSDate *)date
{
    if (!date)
        return;

    if ([date isEqualToDate:[NSDate distantPast]]) {
        [self removeAllObjects];
        return;
    }
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self trimToDate:date block:^(TMMemoryCache *cache) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)trimToCost:(NSUInteger)cost
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self trimToCost:cost block:^(TMMemoryCache *cache) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)trimToCostByDate:(NSUInteger)cost
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self trimToCostByDate:cost block:^(TMMemoryCache *cache) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)removeAllObjects
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self removeAllObjects:^(TMMemoryCache *cache) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

- (void)enumerateObjectsWithBlock:(TMMemoryCacheObjectBlock)block
{
    if (!block)
        return;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self enumerateObjectsWithBlock:block completionBlock:^(TMMemoryCache *cache) {
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    #if !OS_OBJECT_USE_OBJC
    dispatch_release(semaphore);
    #endif
}

#pragma mark - Public Thread Safe Accessors -

- (TMMemoryCacheObjectBlock)willAddObjectBlock
{
    __block TMMemoryCacheObjectBlock block = nil;

    dispatch_sync(_queue, ^{
        block = self->_willAddObjectBlock;
    });

    return block;
}

- (void)setWillAddObjectBlock:(TMMemoryCacheObjectBlock)block
{
    __weak TMMemoryCache *weakSelf = self;
    
    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_willAddObjectBlock = [block copy];
    });
}

- (TMMemoryCacheObjectBlock)willRemoveObjectBlock
{
    __block TMMemoryCacheObjectBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_willRemoveObjectBlock;
    });

    return block;
}

- (void)setWillRemoveObjectBlock:(TMMemoryCacheObjectBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_willRemoveObjectBlock = [block copy];
    });
}

- (TMMemoryCacheBlock)willRemoveAllObjectsBlock
{
    __block TMMemoryCacheBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_willRemoveAllObjectsBlock;
    });

    return block;
}

- (void)setWillRemoveAllObjectsBlock:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_willRemoveAllObjectsBlock = [block copy];
    });
}

- (TMMemoryCacheObjectBlock)didAddObjectBlock
{
    __block TMMemoryCacheObjectBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_didAddObjectBlock;
    });

    return block;
}

- (void)setDidAddObjectBlock:(TMMemoryCacheObjectBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_didAddObjectBlock = [block copy];
    });
}

- (TMMemoryCacheObjectBlock)didRemoveObjectBlock
{
    __block TMMemoryCacheObjectBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_didRemoveObjectBlock;
    });

    return block;
}

- (void)setDidRemoveObjectBlock:(TMMemoryCacheObjectBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_didRemoveObjectBlock = [block copy];
    });
}

- (TMMemoryCacheBlock)didRemoveAllObjectsBlock
{
    __block TMMemoryCacheBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_didRemoveAllObjectsBlock;
    });

    return block;
}

- (void)setDidRemoveAllObjectsBlock:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_didRemoveAllObjectsBlock = [block copy];
    });
}

- (TMMemoryCacheBlock)didReceiveMemoryWarningBlock
{
    __block TMMemoryCacheBlock block = nil;

    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_didReceiveMemoryWarningBlock;
    });

    return block;
}

- (void)setDidReceiveMemoryWarningBlock:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_didReceiveMemoryWarningBlock = [block copy];
    });
}

- (TMMemoryCacheBlock)didEnterBackgroundBlock
{
    __block TMMemoryCacheBlock block = nil;
    
    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        block = self->_didEnterBackgroundBlock;
    });

    return block;
}

- (void)setDidEnterBackgroundBlock:(TMMemoryCacheBlock)block
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_didEnterBackgroundBlock = [block copy];
    });
}

- (NSTimeInterval)ageLimit
{
    __block NSTimeInterval ageLimit = 0.0;
    
    //新加
    __weak typeof(self) weakSelf = self;
    
    dispatch_sync(_queue, ^{
        //ageLimit = _ageLimit; 这种写法有问题
        //改为
        __strong typeof(weakSelf) self = weakSelf;
        
        ageLimit = self->_ageLimit;
    });
    
    return ageLimit;
}

- (void)setAgeLimit:(NSTimeInterval)ageLimit
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;
        
        strongSelf->_ageLimit = ageLimit;
        
        [strongSelf trimToAgeLimitRecursively];
    });
}

- (NSUInteger)costLimit
{
    __block NSUInteger costLimit = 0;

    __weak typeof(self) weakSelf = self;
    
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        costLimit = self->_costLimit;
    });

    return costLimit;
}

- (void)setCostLimit:(NSUInteger)costLimit
{
    __weak TMMemoryCache *weakSelf = self;

    dispatch_barrier_async(_queue, ^{
        TMMemoryCache *strongSelf = weakSelf;
        if (!strongSelf)
            return;

        strongSelf->_costLimit = costLimit;

        if (costLimit > 0)
            [strongSelf trimToCostLimitByDate:costLimit];
    });
}

- (NSUInteger)totalCost
{
    __block NSUInteger cost = 0;
    
    __weak typeof(self) weakSelf = self;
    dispatch_sync(_queue, ^{
        __strong typeof(weakSelf) self = weakSelf;
        cost = self->_totalCost;
    });
    
    return cost;
}

@end
复制代码

PINMemoryCache

由 Pinterest 维护和改进的一个内存缓存。它的功能和接口基本和 TMMemoryCache 一样,但去除了TMMemoryCache不合理的地方,如:使用dispatch_semaphore来加锁,避免了线程切换带来的巨大开销。

具体代码参考: github.com/pinterest/P…

YYMemoryCache

思想都差不多,有些细节上的优化,如:

  • 使用CFMutableDictionaryRef实现对象的异步释放

代码如下:

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);
        }
    }
复制代码
  • 对于清理工作,尽量在别的资源不在操作时才执行

代码如下:

//如果当前其他资源的操作需要锁,那么就不使用锁,10毫秒在来询问,也就是把获取锁的优先级降低
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
}
复制代码

具体代码参考: github.com/ibireme/YYC…

三方不错的源码分析文章: blog.csdn.net/weixin_3397…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript Patterns

JavaScript Patterns

Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99

What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具