NSCache是苹果提供的内存缓存框架。它和NSMutableDictionary和用法很类似,但NSCache是线程安全的,而且没有对Key进行copy。虽然官方文档中没有明确指出缓存淘汰策略,但从测试来看,目前使用的是LRU缓存淘汰策略。node
缓存的大小有限,当缓存被使用满时,咱们须要清除掉部分数据,而清除哪些数据,由缓存淘汰策略决定。常见的算法有三种:git
首先,缓存的数据咱们使用线性结构进行存储。为了知足最近最少使用的策略,咱们须要维持线性表的数据按照使用时间排序。刚插入或者刚使用过的数据时间最近,放在线性表的最前头或者最后面。当缓存满清除数据时,咱们将最近最少使用的数据清除出去,直到知足内存要求。github
线性结构咱们到底应该选数组仍是链表呢?无论使用数组仍是链表,当咱们插入、查找和删除数据时,都须要先根据key查找目标对象,因此时间复杂度都为O(n)。 为了提升查询的效率,咱们须要借助散列表(Hash table)来记录每一个数据的位置。以空间换时间的方式,将查询的时间复杂度下降到O(1)。算法
如今咱们除掉查找查找目标对象的状况下来分析一下使用数组和链表维持LRU的时间复杂度。swift
经过上面的分析能够,咱们在更注重速度的状况下,咱们更倾向于使用链表来维持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的实现会提到。
可是缓存淘汰策略有点奇怪:优先按照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
}
}
复制代码
看源码时发现源码有些问题,没有加锁,同时对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;
});
});
}
复制代码
TMMemoryCache由 Tumblr 开发,但如今已经再也不维护了。特色:
代码以下,含部分修改
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
复制代码
由 Pinterest 维护和改进的一个内存缓存。它的功能和接口基本和 TMMemoryCache 同样,但去除了TMMemoryCache不合理的地方,如:使用dispatch_semaphore来加锁,避免了线程切换带来的巨大开销。
具体代码参考:github.com/pinterest/P…
思想都差很少,有些细节上的优化,如:
代码以下:
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…