手寫NSCache及開源實現的分析

NSCache是什麼

NSCache是蘋果提供的內存緩存框架。它和NSMutableDictionary和用法很類似,但NSCache是線程安全的,而且沒有對Key進行copy。雖然官方文檔中沒有明確指出緩存淘汰策略,但從測試來看,目前使用的是LRU緩存淘汰策略。node

緩存淘汰策略

緩存的大小有限,當緩存被使用滿時,咱們須要清除掉部分數據,而清除哪些數據,由緩存淘汰策略決定。常見的算法有三種:git

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

LRU淘汰策略核心邏輯

首先,緩存的數據咱們使用線性結構進行存儲。爲了知足最近最少使用的策略,咱們須要維持線性表的數據按照使用時間排序。剛插入或者剛使用過的數據時間最近,放在線性表的最前頭或者最後面。當緩存滿清除數據時,咱們將最近最少使用的數據清除出去,直到知足內存要求。github

線性結構咱們到底應該選數組仍是鏈表呢?無論使用數組仍是鏈表,當咱們插入、查找和刪除數據時,都須要先根據key查找目標對象,因此時間複雜度都爲O(n)。 爲了提升查詢的效率,咱們須要藉助散列表(Hash table)來記錄每一個數據的位置。以空間換時間的方式,將查詢的時間複雜度下降到O(1)。算法

如今咱們除掉查找查找目標對象的狀況下來分析一下使用數組和鏈表維持LRU的時間複雜度。swift

  • 數組實現(最近使用的放在最後)
    • 插入操做:若是插入元素不存在時,直接插入到數組尾部,時間複雜度爲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.hbash

#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…

相關文章
相關標籤/搜索