缓存
NSCache
iOS
中体系提供的缓存便是NSCache
还有NSURLCache
,但NSURLCache
的运用则局限于仅仅针关于网络恳求,所以这儿指对NSCache
展开讨论
常用的筛选算法:
-
FIFO(First In First Out)
:先进先出。判别被存储的时刻,离现在最远的数据优先被筛选 -
LRU(Least Recently Used)
:最近最少运用。判别最近被运用的时刻,现在最远的数据优先被筛选 -
LFU(Least Frequently Used)
:最不经常运用。在一段时刻内,数据被运用次数最少的,优先被筛选
NSCache
的开释
NSCache
开释取决于countLimit
、remove
、app进入后台
、内存正告
countLimit
@interface CacheIOP : NSObject<NSCacheDelegate>
@end
@interface ViewController ()
@property (nonatomic, strong) NSCache *cache;
@property (nonatomic, strong) CacheIOP *cacheIOP;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.cacheIOP = [CacheIOP new];
self.cache = [NSCache new];
self.cache.delegate = self.cacheIOP;
self.cache.countLimit = 5;
for (int i = 1; i <= 10; i++) {
[self.cache setObject:[NSString stringWithFormat:@"CacheObject-%d", i] forKey:[NSString stringWithFormat:@"id = %d", i]];
}
[self getCache];
}
- (void)getCache
{
for (int i = 1; i <= 10; i++) {
NSLog(@"%@", [self.cache objectForKey:[NSString stringWithFormat:@"id = %d", i]]);
}
}
@implementation CacheIOP
-(void)cache:(NSCache *)cache willEvictObject:(id)obj
{
NSLog(@"objc:%@ will evict by cache:%@", obj, cache);
}
@end
设置countLimit
后添加到NSCache
的目标超越了约束,则会将之前的数据进行驱赶
app进入后台
能够看到进入后台时会驱赶所有的目标,一起会调用[NSCache removeAllObjects]
其底层符号为cache_remove_all
内存正告⚠️
在模拟器上模拟内存正告
第一次内存正告后
第二次内存正告后
第三次内存正告后将全部铲除
GNUstep 解析 NSCache
现在经过GNUstep
来剖析NSCache
从这儿剖析进入办法setObject:forKey:cost:
,这儿默许是cost
为0
@interface _GSCachedObject : NSObject
{
@public
id object;
NSString *key;
int accessCount;//LFU Least Frequently Used
NSUInteger cost;
BOOL isEvictable;
}
这儿目标都会运用NSMaptable
来存取被包装为_GSCachedObject
的目标,一起accessCount
的作用便是来实现LFU
的筛选战略
- (id) objectForKey: (id)key
{
_GSCachedObject *obj = [_objects objectForKey: key];
if (nil == obj)
{
return nil;
}
if (obj->isEvictable)
{
// Move the object to the end of the access list.
[_accesses removeObjectIdenticalTo: obj];
[_accesses addObject: obj];
}
obj->accessCount++;
_totalAccesses++;
return obj->object;
}
由上能够看到每当在拜访存储在缓存中目标的时分,accessCount
会被操作
- (void) setObject: (id)obj forKey: (id)key cost: (NSUInteger)num
{
_GSCachedObject *oldObject = [_objects objectForKey: key];
_GSCachedObject *newObject;
// 假如key存储了别的目标,那么要将之前目标铲除
if (nil != oldObject)
{
[self removeObjectForKey: oldObject->key];
}
// 依据LRU+LFU的筛选战略来修剪缓存
[self _evictObjectsToMakeSpaceForObjectWithCost: num];
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;
}
下述办法是处理驱赶战略的办法。该实现运用相对简略的LRU/LFU
混合。来自Apple
的NSCache
文档清楚地说明晰战略或许会改动。
- (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;
// 依据LRU排序过的目标数组
NSEnumerator *e = [_accesses objectEnumerator];
_GSCachedObject *obj;
if (_evictsObjectsWithDiscardedContent)
{
evictedKeys = [[NSMutableArray alloc] init];
}
// 经过循环不断删去契合LRU+LFU修剪的目标
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];
}
}
所以在GNUstep
中的NSCache
修剪内存的战略是LRU
+LFU
的混合战略
经过SwiftFoundation
来剖析NSCache
相同也是依据set
办法来找到剖析点,这儿同上述相同
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
private let _lock = NSLock()
private var _totalCost = 0
private var _head: NSCacheEntry<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
}
首先能够看到NSCache
中实际运用字典来存储,可是key
和value
分别是NSCacheKey
和NSCacheEntry
类型
fileprivate class NSCacheKey: 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? 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)
}
}
}
NSCacheKey
作为包装key
的类,其间重写了hash
和isEqual
函数
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
}
}
从上处能够看出NSCache
运用了双向链表结构
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] {// 当前cache中存在此键值对,删去旧值刺进新值
costDiff = g - entry.cost
entry.cost = g
entry.value = obj
if costDiff != 0 {
remove(entry)
insert(entry)
}
} else {// cache中没有此键值对,刺进目标
let entry = NSCacheEntry(key: key, value: obj, cost: g)
_entries[keyRef] = entry
insert(entry)
costDiff = g
}
_totalCost += costDiff
// totalCostLimit 依据此进行内存裁剪,而且是从头结点开始
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
while purgeAmount > 0 {
if let entry = _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
}
}
// countLimit 依据此进行内存裁剪,相同也是从头结点开始
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()
}
/**
刺进节点时的三种情况:
1.cache为空直接将刺进目标作为头结点
2.cache不为空,若刺进节点比头结点的耗费小,将刺进节点设置为头结点
3.cache不为空,若刺进节点比头结点耗费大,找到第一个不比刺进节点耗费小的节点进行刺进,例如刺进节点为5,现在链表为1->3->6->8,则刺进后为1->3->5->6->8
*/
private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
// cache为空的话当做头结点
guard var currentElement = _head else {
// The cache is empty
entry.prevByCost = nil
entry.nextByCost = nil
_head = entry
return
}
// 头结点有值,可是刺进节点的耗费比头结点耗费小,直接置为头结点
guard entry.cost > currentElement.cost else {
// Insert entry at the head
entry.prevByCost = nil
entry.nextByCost = currentElement
currentElement.prevByCost = entry
_head = entry
return
}
// 头结点有值,找到第一个不比刺进节点耗费小的节点进行刺进
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
}
/**
移除节点
*/
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
}
}
从上能够看到,NSCache
是优先依据totalCostLimit
进行裁剪,其次是依据countLimit
,相同文档中写到这两个指标都不是严格精确的,而且这两种裁剪办法或许只会履行其间一个也或许两个都履行,这些都是不能承认的
总结:
NSCache
的缺点:
- 驱赶办法是不承认的
- 内存修剪是不精确的
- 数据或许会被主动开释
NSURLCache
依据NSURLCache官方文档能够知道NSURLCache
是采用内存、磁盘双缓存战略,磁盘缓存的途径是Library/Caches
,而且是以数据库来存储的
接下来经过打开数据库来看看,其间图片便被寄存在这儿
NSURLCache
寄存在沙盒途径下的磁盘缓存
其间寄存:
request
response
receive_data
其间操控缓存的重要字段是Cache-Control
:
-
max-age
:缓存时刻 -
public
:谁都能够缓存 -
private
:只要客户端缓存,中间署理无法缓存 -
no-cache
:服务端进行承认 -
no-store
:制止运用缓存
经过NSURLSessionConfiguration
设置缓存战略:
-
NSURLRequestUseProtocolCachePolicy
:指定现有的缓存数据应该用来满意URL加载恳求,不管它存在多久或过期日期。但是,假如没有缓存中与URL
加载恳求对应的现有数据,URL
从服务器加载 -
NSURLRequestReloadIgnoringLocalCacheData
:指定URL
加载的数据应该从服务器加载 -
NSURLRequestReloadIgnoringLocalAndRemoteCacheData
:不仅要疏忽本地缓存数据,还要疏忽署理的缓存 -
NSURLRequestReloadIgnoringCacheData
:NSURLRequestReloadIgnoringLocalCacheData
的旧称号 -
NSURLRequestReturnCacheDataElseLoad
:有缓存则运用缓存,没有就从服务器恳求 -
NSURLRequestReturnCacheDataDontLoad
:只运用cache数据,假如不存在缓存,恳求失利;用于没有建立网络连接离线模式 -
NSURLRequestReloadRevalidatingCacheData
:是有现有的缓存前有必要与服务器承认其有效性,不然就要从服务器获取
经过下面这段代码能够看出运用ETag
或许lastModified
假如服务器发现恳求的资源并没有发生改动,那么会回来304
意思能够直接运用缓存数据
NSURL *url = [NSURL URLWithString:@"https://www.6hu.cc/wp-content/uploads/2023/02/1676944686-db7bcb20f915775.jpg"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
if (self.lastModified) {
[request setValue:self.lastModified forHTTPHeaderField:@"If-Modified-Since"];
}
if (self.etag) {
[request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"error warning : %@",error);
} else {
NSData *tempData = data;
NSString *responseStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
self.lastModified = [(NSHTTPURLResponse *)response allHeaderFields][@"Last-Modified"];
self.etag = [(NSHTTPURLResponse *)response allHeaderFields][@"Etag"];
NSLog(@"response:%@", response);
}
}] resume];
SDWebImage
中的NSURLCache
定位到SDWebImageDownloaderOperation
中start
办法中
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
self.response = cachedResponse.response;
}
}
这儿能够看到假如设置了疏忽缓存的选项的话,相同需求检查假如有NSURLCache
存在的话是需求将缓存数据读取出来
经过作者的文档标注来看,结构内关于NSURLCache
的运用当地就在于SDWebImageDownloaderUseNSURLCache
和SDWebImageDownloaderIgnoreCachedResponse
,那么接下来的剖析首要是要集中于这两个option
假如设置了运用NSURLCache
的话恳求中的缓存战略运用默许的,假如没有设置则是运用NSURLRequestReloadIgnoringLocalCacheData
,首要便是为了防止重复缓存
这是在将要缓存数据的时分,假如没有设置运用NSURLCache
则将缓存直接置空,那么就缓存不到数据了
在- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
办法中,这儿是网络恳求已经回调了,此时发现设置了疏忽缓存而且缓存的图片和网络回来的是相同的,也便是与之前演示中304
的作用相同,那么给到外面的回调是给一个过错,但这个并不是真的过错,其实便是标识304
,如下
相同在- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
办法中也有
假如标记为304
可是又没有缓存数据,这儿就没有当地能够取数据了
总结:
默许的缓存战略
:
-
客户端发起一个恳求
-
检查本地的缓存:
1. 假如没有过期则直接运用缓存数据 2. 假如过期了,对比服务器资源,服务器回来`304`直接运用缓存,服务器回来`200 `就运用服务器回来的数据
YYCache
YYCache
采纳的缓存筛选算法:LRU(Least Recently Used)
最近最少运用
缓存筛选战略的维度:
count
cost(20KB)
age(距离上一次拜访的时刻)
YYCache
采纳的缓存是内存缓存
和磁盘缓存
的双缓存战略,另外关于磁盘缓存
来讲是采用文件缓存和数据库缓存结合的办法
磁盘缓存
中的区分点:
- 数据巨细大于临界值采用文件缓存
- 数据巨细小于临界值采用数据库缓存
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key];
}
从这儿能够看出是内存缓存和磁盘缓存双缓存的结构
内存缓存
YYMemoryCache
便是管理内存缓存的类
YYMemoryCache
与NSCache
不同的当地在于以下几点:
- 它运用
LRU(最近最少运用)
来删去目标,NSCache
驱赶的办法是不承认的 - 它运用
cost
、count
、age
来操控,NSCache
的约束是不精确的 - 它能够配置为当收到内存正告或应用程序进入后台时主动驱赶目标
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
YYMemoryCache
内部运用双向链表结构,每次运用setObject:forKey:
的办法终究会将目标寄存于双向链表,而且会调整至表头节点,这就刚好契合LRU
的筛选战略,相同裁剪战略会针关于cost(耗费)
以及count(数量)
进行精确的裁剪,便是从链表的尾结点开始裁剪
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {// 假如之前存储过这个key,从头赋值后置为链表的头结点
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {//假如之前没有存储过这个key,从头创建一个节点进行赋值后置为头结点
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
// 存储的总花费比约束的高时进行异步裁剪
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
// 另一个维度在数量上假如超越约束相同进行裁剪
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
//pthread_mutex_trylock() 在成功完成之后会回来零。其他任何回来值都表示出现了过错。假如出现以下任一情况,该函数将失利并回来对应的值
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
// 能够在非主线程中开释目标,holder是个局部变量,假如不这样做只会在函数完成时在主线程内析构
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
相同,在YYMemoryCache
目标存在的情况下会定时对内存缓存的目标从cost(耗费)
、count(数量)
、age(最终一次拜访时刻)
进行检查和裁剪
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
- (void)_trimToAge:(NSTimeInterval)ageLimit {
BOOL finish = NO;
NSTimeInterval now = CACurrentMediaTime();
pthread_mutex_lock(&_lock);
if (ageLimit <= 0) {
[_lru removeAll];
finish = YES;
} else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
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
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
另外能够注意到这儿运用的关键字是__unsafe_unretained
,为什么不运用__weak
?其实这儿便是为了功能考虑,假如运用__weak
会对weak
表进行必定的操作,那么这儿就会发生功能耗费
磁盘缓存
YYCache
的磁盘缓存中YYDiskCache
还需求将存入的目标归档后经过YYKVStorage
的办法来存入数据库,存入数据库时也需求判别存入目标的巨细,假如低于20KB
则直接存入数据库的inline_data
,假如大于20KB
则运用写入文件的办法,一起数据库中filename
就会寄存写入文件的途径,这儿就经过判别是否大于20KB
来实现磁盘缓存中是运用文件写入的办法还是直接将目标存入数据库
寄存的文件结构是以下这样:
File:
/path/
/manifest.sqlite
/manifest.sqlite-shm
/manifest.sqlite-wal
/data/
/e10adc3949ba59abbe56e057f20f883e
/e10adc3949ba59abbe56e057f20f883e
/trash/
/unused_file_or_folder
SQL:
create table if not exists manifest (
key text,
filename text,
size integer,
inline_data blob,
modification_time integer,
last_access_time integer,
extended_data blob,
primary key(key)
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
NSData *value = nil;
if (_customArchiveBlock) {//假如有自定义的归档办法
value = _customArchiveBlock(object);
} else {//运用默许的归档办法
@try {
value = [NSKeyedArchiver archivedDataWithRootObject:object];
}
@catch (NSException *exception) {
// nothing to do...
}
}
if (!value) return;
NSString *filename = nil;
if (_kv.type != YYKVStorageTypeSQLite) {
if (value.length > _inlineThreshold) {
//假如目标大于20kb,生成一个MD5的文件名
filename = [self _filenameForKey:key];
}
}
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
if (key.length == 0 || value.length == 0) return NO;
if (_type == YYKVStorageTypeFile && filename.length == 0) {
return NO;
}
if (filename.length) {
//写入文件
if (![self _fileWriteWithName:filename data:value]) {
return NO;
}
// 存入数据库,这儿要看是否存在filename,假如存在则inline_data为空,运用文件存取,不然目标存入inline_data
if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
} else {
if (_type != YYKVStorageTypeSQLite) {
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
[self _fileDeleteWithName:filename];
}
}
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
数据库和寄存的文件如下图所示: