NSCache是Foundation 框架中用于缓存的类(内存缓存)。其使用方法类似于Dictionary。其源码实现比较简单,但是也挺坑人的尤其是
totalCostLimit
和countLimit
属性。关键代码处我都加了注释。
//
// Cache.swift
//
//
// Created by Nigel.He on 16/11/17.
//
//
//
// NSCache 中保存的实体
import Foundation
class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
var key: KeyType
var value: ObjectType
var cost: Int
var prevByCost: NSCacheEntry?
var nextByCost: NSCacheEntry?
init(key: KeyType, value: ObjectType, cost: Int) {
self.key = key
self.value = value
self.cost = cost
}
}
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
//保存缓存的对象
var _entries = Dictionary<UnsafeRawPointer, NSCacheEntry<KeyType, ObjectType>>()
//同步锁,使线程安全
private let _lock = NSLock()
//当前的Cost
private var _totalCost = 0
//指向链表的头。 链表的头节点cost值最小,尾节点cost值最大
private var _byCost: NSCacheEntry<KeyType, ObjectType>?
open var name: String = ""
//限制不精确,不严格
open var totalCostLimit: Int = -1 // limits are imprecise/not strict
open var countLimit: Int = -1 // limits are imprecise/not strict
open var evictsObjectsWithDiscardedContent: Bool = false
public override init() {}
open weak var delegate: NSCacheDelegate?
// 根据key取value
open func object(forKey key: KeyType) -> ObjectType? {
var object: ObjectType?
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
_lock.lock()
if let entry = _entries[keyRef] {
object = entry.value
}
_lock.unlock()
return object
}
//使用key做标示,缓存value. 默认cost=0
open func setObject(_ obj: ObjectType, forKey key: KeyType) {
setObject(obj, forKey: key, cost: 0)
}
//设置缓存,并为每个节点设置一个cost。
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
_lock.lock()
_totalCost += g
var purgeAmount = 0
if totalCostLimit > 0 {
//purgeAmount = (_totalCost + g) - totalCostLimit
purgeAmount = _totalCost - totalCostLimit
}
var purgeCount = 0
if countLimit > 0 {
purgeCount = (_entries.count + 1) - countLimit
}
//这里就非常有意思了,因为只有_entries[keyRef] 存在值时才会创建链表,否则是不会创建出链表结构。
//这里就是万恶之源呀。没有正确的链表,totalCostLimit,countLimit的行为就会受到影响。,也就导致了countLimit和totalCostLimit限制不精确,不严格。
//我只想说,这样也可以!!!算七八糟呀!
if let entry = _entries[keyRef] {
entry.value = obj
if entry.cost != g {
entry.cost = g
//先在链表中删除源节点,然后再加到链表中
remove(entry)
insert(entry)
}
} else {
//这里的的代码导致了
_entries[keyRef] = NSCacheEntry(key: key, value: obj, cost: g)
}
_lock.unlock()
// 针对totalCostLimit,移除一些节点。 整理准确执行的先决条件是 创建出准确的链表
var toRemove = [NSCacheEntry<KeyType, ObjectType>]()
if purgeAmount > 0 {
_lock.lock()
while _totalCost - totalCostLimit > 0 {
if let entry = _byCost {
_totalCost -= entry.cost
toRemove.append(entry)
remove(entry)
} else {
break
}
}
if countLimit > 0 {
purgeCount = (_entries.count - toRemove.count) - countLimit
}
_lock.unlock()
}
//针对countLimit,移除一些节点。 整理准确执行的先决条件是 创建出准确的链表
if purgeCount > 0 {
_lock.lock()
while (_entries.count - toRemove.count) - countLimit > 0 {
if let entry = _byCost {
_totalCost -= entry.cost
toRemove.append(entry)
remove(entry)
} else {
break
}
}
_lock.unlock()
}
//通知代理删除了节点
if let del = delegate {
for entry in toRemove {
del.cache(unsafeBitCast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
}
}
_lock.lock()
//在字典中移除节点
for entry in toRemove {
_entries.removeValue(forKey: unsafeBitCast(entry.key, to: UnsafeRawPointer.self)) // the cost list is already fixed up in the purge routines
}
_lock.unlock()
}
//产生双向的链表。 链表的头节点cost值最小,尾节点cost值最大
private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
if _byCost == nil { // 第一次,初始化
_byCost = entry
} else {
var element = _byCost
while let e = element {
if e.cost > entry.cost {
let newPrev = e.prevByCost
entry.prevByCost = newPrev
entry.nextByCost = e
break
}
element = e.nextByCost
}
}
}
//根据key, 删除一个节点
open func removeObject(forKey key: AnyObject) {
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
_lock.lock()
if let entry = _entries.removeValue(forKey: keyRef) {
_totalCost -= entry.cost
remove(entry)
}
_lock.unlock()
}
//删除一个节点
private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
let oldPrev = entry.prevByCost
let oldNext = entry.nextByCost
oldPrev?.nextByCost = oldNext
oldNext?.prevByCost = oldPrev
if entry === _byCost {
_byCost = entry.nextByCost
}
}
//删除全部
open func removeAllObjects() {
_lock.lock()
_entries.removeAll()
_byCost = nil
_totalCost = 0
_lock.unlock()
}
}
public protocol NSCacheDelegate : NSObjectProtocol {
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: AnyObject)
}
extension NSCacheDelegate {
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: AnyObject) {
// Default implementation does nothing
}
}