缓存

NSCache

iOS中体系提供的缓存便是NSCache还有NSURLCache,但NSURLCache的运用则局限于仅仅针关于网络恳求,所以这儿指对NSCache展开讨论

常用的筛选算法:

  • FIFO(First In First Out):先进先出。判别被存储的时刻,离现在最远的数据优先被筛选
  • LRU(Least Recently Used):最近最少运用。判别最近被运用的时刻,现在最远的数据优先被筛选
  • LFU(Least Frequently Used):最不经常运用。在一段时刻内,数据被运用次数最少的,优先被筛选

NSCache的开释

NSCache开释取决于countLimitremoveapp进入后台内存正告

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

谈谈在iOS中使用的缓存

设置countLimit后添加到NSCache的目标超越了约束,则会将之前的数据进行驱赶

app进入后台

谈谈在iOS中使用的缓存

谈谈在iOS中使用的缓存

能够看到进入后台时会驱赶所有的目标,一起会调用[NSCache removeAllObjects]其底层符号为cache_remove_all

内存正告⚠️

在模拟器上模拟内存正告

谈谈在iOS中使用的缓存

第一次内存正告后

谈谈在iOS中使用的缓存

第二次内存正告后

谈谈在iOS中使用的缓存

第三次内存正告后将全部铲除

谈谈在iOS中使用的缓存

GNUstep 解析 NSCache

现在经过GNUstep来剖析NSCache

谈谈在iOS中使用的缓存

从这儿剖析进入办法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混合。来自AppleNSCache文档清楚地说明晰战略或许会改动。

- (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

谈谈在iOS中使用的缓存

相同也是依据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中实际运用字典来存储,可是keyvalue分别是NSCacheKeyNSCacheEntry类型

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的类,其间重写了hashisEqual函数

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,而且是以数据库来存储的

谈谈在iOS中使用的缓存

接下来经过打开数据库来看看,其间图片便被寄存在这儿

谈谈在iOS中使用的缓存

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/files/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];

谈谈在iOS中使用的缓存

SDWebImage中的NSURLCache

定位到SDWebImageDownloaderOperationstart办法中

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存在的话是需求将缓存数据读取出来

谈谈在iOS中使用的缓存

经过作者的文档标注来看,结构内关于NSURLCache的运用当地就在于SDWebImageDownloaderUseNSURLCacheSDWebImageDownloaderIgnoreCachedResponse,那么接下来的剖析首要是要集中于这两个option

谈谈在iOS中使用的缓存

假如设置了运用NSURLCache的话恳求中的缓存战略运用默许的,假如没有设置则是运用NSURLRequestReloadIgnoringLocalCacheData,首要便是为了防止重复缓存

谈谈在iOS中使用的缓存

这是在将要缓存数据的时分,假如没有设置运用NSURLCache则将缓存直接置空,那么就缓存不到数据了

谈谈在iOS中使用的缓存

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error办法中,这儿是网络恳求已经回调了,此时发现设置了疏忽缓存而且缓存的图片和网络回来的是相同的,也便是与之前演示中304的作用相同,那么给到外面的回调是给一个过错,但这个并不是真的过错,其实便是标识304,如下

谈谈在iOS中使用的缓存

相同在- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler办法中也有

谈谈在iOS中使用的缓存

假如标记为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便是管理内存缓存的类

YYMemoryCacheNSCache不同的当地在于以下几点:

  • 它运用LRU(最近最少运用)来删去目标,NSCache驱赶的办法是不承认的
  • 它运用costcountage来操控,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
        });
    }
}

谈谈在iOS中使用的缓存

另外能够注意到这儿运用的关键字是__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];
    }
}

数据库和寄存的文件如下图所示:

谈谈在iOS中使用的缓存

谈谈在iOS中使用的缓存