1:NSCache
1)NSCache是Foundation框架提供的缓存类。使用方法类似于字典。 它遵循NSCacheDelegate协议。具有
- (nullable ObjectType)objectForKey:(KeyType)key;
- (void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
- (void)removeObjectForKey:(KeyType)key;
- (void)removeAllObjects;
方法。通过key-value进行查找添加设置删除元素。cost默认是0会影响缓存策略。
2)特点
- 它是线程安全的。NSMutableDictionary是线程不安全的。
- 内存不足,NSCache会自动释放存储对象。
- NSCache的key不会被拷贝,不需要实现Copying协议,不需要拷贝。
- 当超过totalCostLimit会被挤掉,旧的会被释放。
- 可以通过手动调用remove进行手动的释放。
- 当按home键在app进入后台之后,NSCache对象会进行自动释放所有对象。
- 如果在模拟器上模拟内存警告。NsCache不会释放对象。
- 如果NSCache遵循NSDiscardableContent标示当前对象是可被清除的。提高缓存的淘汰。系统会将内存上的存储置换到磁盘上,如果一个内存被标记为
purgabel可被清除的。就不会置换直接被清除。
使用NSPurgeableData
UIImage *image = [UIImage imageNamed:@"logo"];;
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
/**
计数器
count >=1 ,使用
count == 0, 丢弃的
*/
_testPurgeableData = [[NSPurgeableData alloc] initWithData:(__bridge NSData *)rawData];
@interface NSPurgeableData : NSMutableData <NSDiscardableContent> {
@private
NSUInteger _length;
int32_t _accessCount;
uint8_t _private[32];
void *_reserved;
}
可被清除取决于计数器count >=1 当前对象可以被使用访问 == 0 当前对象会被丢弃的不可访问。与引用计数不同。通过NSDiscardableContent进行控制
@protocol NSDiscardableContent
@required
- (BOOL)beginContentAccess;//count+1
- (void)endContentAccess;//count-1
- (void)discardContentIfPossible;
- (BOOL)isContentDiscarded;
@end
3)两个重要的指标(缓存策略)
- 通过设置countLimit缓存数据的数目,和设置totalCostLimit混存数据占用内存大小。来控制混存释放的时机。
- totalCostLimit 总消耗大小,超过会做混存的修剪操作。但无法确定丢弃的顺序。因为依赖于底层的算法淘汰策略是不一样的。
- countLimit数量限制
2:NSCache 使用实例
1:_evictsObjectsWithDiscardedContent如果实现这个协议就标示被移除掉 与purgabel相同。
2:NSMapTable * _objects;``key内存管理语义,肯定不是copy
3:setObject: forKey实现
- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num
{
_GSCachedObject *oldObject = [_objects objectForKey: key];
_GSCachedObject *newObject;
if (nil != oldObject)
{
[self removeObjectForKey: oldObject->key];//移除旧的
}
//缓存的淘汰
[self _evictObjectsToMakeSpaceForObjectWithCost: num];//cost当前的消耗默认是0
newObject = [_GSCachedObject new];
// Retained here, released when obj is dealloc'd
newObject->object = RETAIN(obj);
newObject->key = RETAIN(key);
newObject->cost = num;
if ([obj conformsToProtocol: @protocol(NSDiscardableContent)])
{
newObject->isEvictable = YES;
[_accesses addObject: newObject];
}
[_objects setObject: newObject forKey: key];
RELEASE(newObject);
_totalCost += num;
}
3.2cost如何计算消耗的
- (void)_evictObjectsToMakeSpaceForObjectWithCost: (NSUInteger)cost
{
NSUInteger spaceNeeded = 0;
NSUInteger count = [_objects count];
if (_costLimit > 0 && _totalCost + cost > _costLimit)
{
spaceNeeded = _totalCost + cost - _costLimit; // 计算要清除的空间
}
// Only evict if we need the space.
if (count > 0 && (spaceNeeded > 0 || count >= _countLimit))
{
NSMutableArray *evictedKeys = nil;
// Round up slightly.
NSUInteger averageAccesses = ((_totalAccesses / (double)count) * 0.2) + 1;//平均访问次数。_totalAccesses是所有访问次数的总和根据二八原则,+1保证结果不为0。
NSEnumerator *e = [_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])
{
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];//移除
}
}
[evictedKeys release];
}
}
注:关键是根据计算的averageAccesses频次进行移除。低于的都被释放掉。
4:_GSCachedObject
@interface _GSCachedObject : NSObject
{
@public
id object;
NSString *key;
int accessCount;//对象能够被访问的次数
NSUInteger cost;//内存消耗
BOOL isEvictable;//是否能够被驱逐,移除
}
@end
3:NSCache 使用实例
Swfit Foundation 源码地址
1: setObject函数
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
let g = max(g, 0)
let keyRef = NSCacheKey(key)
_lock.lock()
let costDiff: Int
if let entry = _entries[keyRef] {
costDiff = g - entry.cost //costDiff有值代表当前已经缓存过对象了
entry.cost = g // 更新当前值的消耗大小
entry.value = obj // 更新缓存对象
if costDiff != 0 {
remove(entry) //先移除更新链表结构
insert(entry) //后添加
}
} else {
let entry = NSCacheEntry(key: key, value: obj, cost: g)
_entries[keyRef] = entry
insert(entry)
costDiff = g
}
_totalCost += costDiff
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0 //totalCostLimit默认不设置不走这段 无意义
while purgeAmount > 0 {
if let entry = _head { //_head链表的头节点
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
_totalCost -= entry.cost
purgeAmount -= entry.cost
remove(entry) // _head will be changed to next entry in remove(_:)
_entries[NSCacheKey(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:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
_totalCost -= entry.cost
purgeCount -= 1
remove(entry) // _head will be changed to next entry in remove(_:)
_entries[NSCacheKey(entry.key)] = nil
} else {
break
}
}
_lock.unlock()
}
2: remove(entry)函数实现,更新双向链表结构,做修改的操作。
private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
let oldPrev = entry.prevByCost
let oldNext = entry.nextByCost
oldPrev?.nextByCost = oldNext
oldNext?.prevByCost = oldPrev
if entry === _head {
_head = oldNext
}
}
3:insert(entry)函数实现主要注意点
/***先做清空操作***/
......
guard entry.cost > currentElement.cost else { //依据cost进行排序新插入的如果大于当前节点消耗,把当前的插入放在最前面。如果cost为0不进行排序。
// Insert entry at the head
entry.prevByCost = nil
entry.nextByCost = currentElement
currentElement.prevByCost = entry
_head = entry
return
}
......
currentElement.nextByCost = entry //插入当前链表
4:NSCacheKeykey值 NSCacheEntry内容
private 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
}
}
5:为了保证key值的唯一性。重写了hash函数和isEqual函数。
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? NSCacheKey) 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)
}
}
6:使用NSLock保证线程读写安全。
7:purgeAmount和purgeCount缓存策略.背后也是根据 totalCostLimit和countLimit缓存策略。当两个都设置的时按顺序执行。totalCostLimit也不是严格的限制,会依据缓存策略的不同进行移除。
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
}
var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
}
4:NSURLCache
1:使用NSURLCache建立缓存系统。
- 可以指定缓存的磁盘和内存的大小。
NSURLCache * cache = [[NSURLCache sharedURLCache] initWithMemoryCapacity:10*1024*1024 diskCapacity:30*1024*1024 diskPath:@"文件保存路径"];
- 主要用于对网络请求的缓存。Get请求
cache-Control默认选项
1)max-age 缓存时间
2)public:谁都可以缓存
3)private 只有客户端进行缓存,比如中间的代理是无法缓存的。
4)no-cache并不代表不缓存,只不过要跟服务端进行确认,如果没变化使用本地缓存,如果有变化使用服务器端。
5) no-store禁止使用缓存。
地址:Library-->cache
2:内存缓存和磁盘缓存。
3: CachePolicy 沙盒中CFNetwork开头的缓存
useProtocalCachePolicy默认缓存策略,对于特定URL使用网络协议中实现的缓存策略。(HTTP)reloadIgnoringLocalCacheData(或者reloadIngnoringCacheData):不使用缓存直接请求原始数据。returnCacheDataElseLoad:无论缓存是否过期,有缓存则使用缓存,否则重新请求原始数据。returnCacheDataDontLoad:无论缓存是否过期,有缓存则使用缓存,否则视为失效。不会重新请求原始数据。
4:lastModified比对服务,资源是否更新。Code = 200 代表请求数据。Code = 304 代表在缓存中读取数据


5:etag看服务器是否支持与lastModified作用相同。区别在于lastModified通过日期进行比较。有可能会出现判断的错误。而etag是通过当前的服务器生成hash值进行比对,更准确。但平时使用lastModified就够了。
6:缓存到沙盒当中的。
5:SDEWebImage如何配置缓存策略的
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2, //默认忽略当前缓存。需要去服务器进行请求。为了避免重复缓存。
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};