YYMemoryCache的源码分析

本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java, 数据结构与算法, iOS, 安卓, python, flutter等等, 如有需要, 联系微信tsaievan.

YYCache将缓存分成了两部分:
  • 内存缓存
  • 磁盘缓存
YYMemoryCacheYYCache的内存缓存.

YYMemoryCache是一个快捷的内存缓存, 缓存键值对
与NSDictionary不同, keys是被持有的而不是被拷贝的
API和性能与NSCache一致, 所有方法都是线程安全的

YYMemoryCache与NSCache的不同点

  • 使用了LRU(最近最少被使用)算法去移除缓存对象
  • 可以设置开销去控制缓存容量,NSCache的缓存限制是不精确的
  • 当收到内存警告或者是app进入后台时, 可以自动清除缓存对象
先来看YYMemoryCache是如何使用的:
@interface YYMemoryCache : NSObject

#pragma mark - 属性

///< 缓存的名称
@property (nullable, copy) NSString *name;

///< 缓存对象的总数, 只读
@property (readonly) NSUInteger totalCount;

///< 缓存对象的总开销, 只读
@property (readonly) NSUInteger totalCost;


#pragma mark - 限制

///< 缓存的最大容量
@property NSUInteger countLimit;

///< 在开始移除缓存对象前缓存的最大开销
@property NSUInteger costLimit;

///< 缓存的最大失效时间
@property NSTimeInterval ageLimit;

///< 自检时间, 默认5秒, 递归调用, 自检缓存是否超过了限制, 如果超过了限制, 会移除缓存
@property NSTimeInterval autoTrimInterval;

///< 收到内存缓存是否移除所有缓存对象, 默认YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;

///< 程序进入后台是否移除所有缓存对象, 默认YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;

///< app收到内存警告的时候调用的block, 默认为nil
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);

///< 程序进入后台之后调用的block, 默认为nil
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);

///< 键值对是否在主线程释放, YES: 主线程, NO: 子线程
///< 默认为NO
///< 必须设置为YES, 如果键值对中包含必须在主线程释放的对象(比如 UIView/ CALayer)
@property BOOL releaseOnMainThread;

///< 是否异步释放以避免阻塞访问内存缓存的方法, 否则要在访问方法中释放 (例如removeObjectForKey:)
///< 默认是YES(即异步释放)
@property BOOL releaseAsynchronously;


#pragma mark - 访问方法

///< 缓存中是否包含某一个key
- (BOOL)containsObjectForKey:(id)key;

///< 从缓存中取key对应的对象
- (nullable id)objectForKey:(id)key;

///< 向缓存中存对象
///< 如果key为nil. 直接return
///< 如果object为nil. 则清除key对应的对象
- (void)setObject:(nullable id)object forKey:(id)key;

///< 向缓存中存对象
///< cost是开销, 和键值对关联的
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;

///< 清除缓存中某个key对应的对象
- (void)removeObjectForKey:(id)key;

///< 清除缓存中所有对象
- (void)removeAllObjects;

#pragma mark - 部分清除缓存

///< 利用LRU(最近最少使用 least recently used)算法, 从尾节点清除缓存, 直到总数小于等于设定的限制值
- (void)trimToCount:(NSUInteger)count;

///< 从尾节点清除缓存, 直到总开销小于等于设定的限制值
- (void)trimToCost:(NSUInteger)cost;

///< 从尾节点清除缓存, 将所有过期的缓存对象全部删除
///< age表示对象缓存的最长时间(单位是秒)
- (void)trimToAge:(NSTimeInterval)age;

@end
通过YYMemoryCache暴露的接口得知:
接口总共分为四大块:
  • 属性
  • 限制
  • 访问方法
  • 清除部分缓存
<一>属性中:

你可以为缓存设置name, 便于管理不同的缓存
totalCounttotalCost是只读的, 你可以通过这个属性, 获取到当前缓存的缓存对象的总数总开销

<二>限制中:

有三大维度限制缓存的容量:

  • countLimit: 缓存的最大容量
  • costLimit: 在开始移除缓存对象前缓存的最大开销
  • ageLimit: 缓存的最大失效时间

以及一些其他的属性:

  • autoTrimInterval : 自检时间, 默认5秒, 递归调用, 自检缓存是否超过了限制, 如果超过了限制, 会移除缓存.

  • shouldRemoveAllObjectsOnMemoryWarning: 收到内存缓存是否移除所有缓存对象, 默认YES

  • shouldRemoveAllObjectsWhenEnteringBackground : 程序进入后台是否移除所有缓存对象, 默认YES

  • didReceiveMemoryWarningBlock : app收到内存警告的时候调用的block, 默认为nil

  • didEnterBackgroundBlock : 程序进入后台之后调用的block, 默认为nil

  • releaseOnMainThread : 键值对是否在主线程释放, YES: 主线程, NO : 子线程 . 默认为NO, 如果键值对中包含必须在主线程释放的对象(比如 UIView/ CALayer), 此时必须设置为YES.

  • releaseAsynchronously : 是否异步释放以避免阻塞访问内存缓存的方法, 否则要在访问方法中释放 (例如removeObjectForKey:), 默认是YES(即异步释放)

<三>访问方法:

访问方法其实也就是,, , 的过程, 这几个访问方法跟传统的例如NSCache的方法其实是差不多的.

  • 增:
  • 改:
///< 向缓存中存对象
///< 如果key为nil. 直接return
///< 如果object为nil. 则清除key对应的对象
- (void)setObject:(nullable id)object forKey:(id)key;

///< 向缓存中存对象
///< cost是开销, 和键值对关联的
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
  • 删:
///< 清除缓存中某个key对应的对象
- (void)removeObjectForKey:(id)key;

///< 清除缓存中所有对象
- (void)removeAllObjects;
  • 查:
///< 缓存中是否包含某一个key
- (BOOL)containsObjectForKey:(id)key;

///< 从缓存中取key对应的对象
- (nullable id)objectForKey:(id)key;

<四>部分清除缓存:

这个主要还是围绕三大维度来进行操作的:

  • count : 缓存对象的数量
///< 利用LRU(最近最少使用 least recently used)算法, 从尾节点清除缓存, 直到总数小于等于设定的限制值
- (void)trimToCount:(NSUInteger)count;
  • cost : 缓存对象的开销
///< 从尾节点清除缓存, 直到总开销小于等于设定的限制值
- (void)trimToCost:(NSUInteger)cost;
  • age : 缓存要保存的时间
///< 从尾节点清除缓存, 将所有过期的缓存对象全部删除
///< age表示对象缓存的最长时间(单位是秒)
- (void)trimToAge:(NSTimeInterval)age;
再来看看YYMemoryCache内部的实现原理:

YYMemoryCache内部是用字典 + 链表来实现的:
字典用来存储数据, 数据是被包装在node(节点)对象中的
链表用来实现LRU算法, 实现从三大维度层面上管理内存缓存

YYMemoryCache中的链表是一个双向链表_YYLinkedMap:

@interface _YYLinkedMap : NSObject {
    @package
    CFMutableDictionaryRef _dic; // do not set object directly
    NSUInteger _totalCost;
    NSUInteger _totalCount;
    ///< 头部节点
    _YYLinkedMapNode *_head; // MRU, 最近经常使用
    
    ///< 尾部节点
    _YYLinkedMapNode *_tail; // LRU, 最近不常使用
    BOOL _releaseOnMainThread;
    BOOL _releaseAsynchronously;
}

_YYLinkedMap的成员变量来看:
_YYLinkedMap主要由三个部分组成:

  • 一个字典
  • 头部节点
  • 尾部节点

链表中的每一个节点都封装成了一个对象_YYLinkedMapNode:

@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;
}

节点中包含了指向上一个节点的指针_prev
以及指向下一个节点的指针_next

双向链表的数据结构如下图所示:

YYMemoryCache的源码分析_第1张图片
YYMemoryCache中的双向链表

_YYLinkedMapNode中还有两个成员变量

  • key
  • value
    这样就把需要保存的键值对(key-value)封装成了一个对象.

而链表_YYLinkedMap中的字典保存的实际上就是一个个的节点_YYLinkedMapNode, 而每一个节点_YYLinkedMapNode都能根据_prev_next指针找到上一个和下一个节点

基本的数据结构介绍完了, 那我们就来看看YYMemoryCache是怎么跑起来的:

这是YYMemoryCache的初始化方法:

- (instancetype)init {
    self = super.init;
    
    ///< 初始化一把锁
    pthread_mutex_init(&_lock, NULL);
    
    ///< 创建一个新的链表
    _lru = [_YYLinkedMap new];
    
    ///< 创建一个串行队列
    _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
    
    _countLimit = NSUIntegerMax;
    _costLimit = NSUIntegerMax;
    _ageLimit = DBL_MAX;
    _autoTrimInterval = 5.0;
    _shouldRemoveAllObjectsOnMemoryWarning = YES;
    _shouldRemoveAllObjectsWhenEnteringBackground = YES;
    
    ///< 系统通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
    
    [self _trimRecursively];
    return self;
}

初始化方法里面做了这几件事:

  • 初始化一把锁
  • 创建一个链表对象
  • 创建一个串行队列
  • 对一些成员变量进行初始化赋值
  • 注册通知
  • 递归调用清除缓存的方法

我们就从递归调用清除缓存的方法中看看内部是怎么实现的:

///< 递归清除缓存
- (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];
    });
}

这个方法先是开了一个GCD定时器, 然后定时在子线程中清除缓存_trimInBackground, 然后再递归调用这个方法_trimRecursively

_trimInBackground方法中就是从三个维度去清除缓存

  • _trimToCost
  • _trimToCount
  • _trimToAge
我们就以_trimToCount来说明他是怎么从缓存数量这个维度去清除缓存的:
///< 清除缓存到缓存数量的上限
- (void)_trimToCount:(NSUInteger)countLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock); ///< 加锁
    if (countLimit == 0) { ///< 如果缓存数量限制为0, 就清除全部节点
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCount <= countLimit) { ///< 总缓存数量小于等于缓存数量限制
        finish = YES;
    }
    pthread_mutex_unlock(&_lock); ///< 解锁
    if (finish) return; ///< 如果finish为YES, 直接return
    
    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) { ///< 如果能锁成功就加锁, 执行后面的操作
            if (_lru->_totalCount > countLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode]; ///< 移除尾节点
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else { ///< 如果不能锁成功, 就线程阻塞10毫秒,然后再尝试加锁, 执行remove操作
            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
        });
    }
}
  • 首先, 初始化一个BOOL类型的指示变量finish, 初始值为NO
  • 在对链表进行读写时, 要加上线程锁
  • 如果我们设置的数量上限countLimit0的话, 那么就是清除链表中的全部节点, finish置为YES
  • 如果当前链表中节点的总数量小于等于countLimit, 那就什么都不做, finish置为YES
  • 如果链表中的节点总数大于countLimit, 那就要逐一去掉尾部节点, 直到链表中节点的总数量小于等于countLimit.

那么, 实现这个需求, 最好的方法就是开启一个while循环:

    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) { ///< 如果能锁成功就加锁, 执行后面的操作
            if (_lru->_totalCount > countLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode]; ///< 移除尾节点
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else { ///< 如果不能锁成功, 就线程阻塞10毫秒,然后再尝试加锁, 执行remove操作
            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
        });
    }

在这个清除缓存的方法中, 最重要的其实就是两个方法:

  • removeAll 清除所有节点
///< 移除所有
- (void)removeAll {
    ///< 总开销为0
    _totalCost = 0;
    ///< 总节点数为0
    _totalCount = 0;
    
    ///< 头部节点和尾部节点都置空
    _head = nil;
    _tail = nil;
    
    
    if (CFDictionaryGetCount(_dic) > 0) {
        
        ///< 创建一个新的变量去接这个字典的地址
        CFMutableDictionaryRef holder = _dic;
        
        ///< 重新建一个可变字典赋值给_dic
        _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        ///< 异步释放
        ///< 把原来的_dic地址指向的内存空间释放掉了
        ///< _dic指向了新的地址
        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);
        }
    }
}

思路就是:

  1. 将一些成员变量全部置空, 比如头部节点, 尾部节点, 总开销, 总数量
  2. 用一个变量接收老的_dic变量
  3. 新创建一个字典赋值给_dic
  4. 将字典中保存的键值对异步释放
  • removeTailNode 清除尾部节点
///< 移除尾部节点, 有返回值
- (_YYLinkedMapNode *)removeTailNode {
    ///< 如果不存在尾部节点, 直接返回nil
    if (!_tail) return nil;
    _YYLinkedMapNode *tail = _tail;
    
    ///< 通过尾部节点的key值, 从字典中移除尾部节点
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
    
    ///< 总开销中减去尾部节点的开销
    _totalCost -= _tail->_cost;
    
    ///< 缓存的总节点数减去1
    _totalCount--;
    if (_head == _tail) { ///< 如果头部节点等于尾部节点, 那么说明链表中只有一个节点
        _head = _tail = nil; ///< 那么将头部节点和尾部节点都置空即可
    } else { ///< 表明链表中不止一个节点
        _tail = _tail->_prev; ///< 将尾部节点的上一个节点赋值给尾部节点
        _tail->_next = nil; ///< 尾部节点的下一个节点赋值为nil
    }
    
    ///< 此时返回的是移除的尾部节点的地址, 而现在的_tail的地址已经指向了当前的尾部节点的地址
    return tail;
}

思路就是:

  1. 先保存尾部节点的旧值, 这个是最后要返回出去的
  2. 通过尾部节点的key值, 从字典中移除尾部节点
  3. 总开销中减去尾部节点的开销
  4. 缓存的总节点数减去1
  5. 如果头部节点等于尾部节点, 那么说明链表中只有一个节点,那么将头部节点和尾部节点都置空即可
  6. 否则, 表明链表中不止一个节点, 将尾部节点的上一个节点赋值给尾部节点, 尾部节点的下一个节点赋值为nil

YYMemoryCache中的访问方法中的清除全部缓存removeAllObjects 实际上就是线程安全地调用链表中的清除全部节点的方法

那我们再看看YYMemoryCache中的利用key是怎么实现的:
<一> 存:
///< 将对象存缓存
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    if (!key) return; ///< 如果key为空, 直接return 
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
    pthread_mutex_lock(&_lock);
    ///< 先根据key值, 取出node
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    NSTimeInterval now = CACurrentMediaTime();
    
    ///< 存数据的时候, 把最近使用的放在最前面的!
    if (node) { ///< 如果node不为空
        _lru->_totalCost -= node->_cost; ///< 总开销先减去node的开销
        _lru->_totalCost += cost; ///< 再加上当前传入的参数的开销
        node->_cost = cost;
        node->_time = now;
        node->_value = object;
        [_lru bringNodeToHead:node]; ///< 将节点移动到头部
    } else { ///< 如果node为空
        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);
}

  • 如果key为空, 直接return
  • 如果object为空, 则调用removeObjectForKey, 将key对应的值清掉
  • 如果keyobject都不为空, 这个时候就开始
  • 首先, 根据key值取出节点node
  • 获取当前的时间
  • 进行判断
  1. 如果node存在, 将链表的总开销减去当前node的开销, 然后再将链表的总开销加上传入的参数cost, nodecost赋值新值, nodetime赋值当前时间, nodevalue赋值object, 最后将node移动到头部
  2. 如果node为空, 则新建一个node. 再将nodecost, time, key, value依次赋值, 然后将此node插入到头部
  • 最后就是盘点链表的总开销和缓存对象的总数量是否大于限定值, 如果大于, 就要进行清除.
<二>取:
///< 从字典中取出节点, 并将节点的时间变量更新, 并将节点移动到链表头部
- (id)objectForKey:(id)key {
    if (!key) return nil;
    pthread_mutex_lock(&_lock);
    ///< 从字典中取出节点
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        ///< 节点s的时间属性的更新
        node->_time = CACurrentMediaTime();
        ///< 将节点移动到链表头部
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil; ///< 如果节点不存在, 返回nil
}

取相对于存就比较简单了:

  • 根据key从链表中取出节点node
  • 如果node存在, 就将nodetime更新为当前时间
  • 并将此节点移动到链表头部
  • 最后, 利用三元表达式, 判断node是否为空, 如果不为空, 则将nodevalue返回出去, 如果为空, 则返回nil
关于链表的操作, 有以下几个方法:
  • insertNodeAtHead:在头部插入一个节点, 并更新总数
  • bringNodeToHead:将内部节点移动到头部
  • removeNode:移除一个内部节点, 并更新总数
  • removeTailNode 如果存在的话, 移除尾节点
  • removeAll在子线程中移除所有节点

具体的实现我就不做具体介绍了, 下面是代码, 以及我翻译的注释,大家一看就能明白

///< 将节点插入到头部
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
    ///< 先将节点存到字典中去
    CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
    _totalCost += node->_cost;
    _totalCount++;
    if (_head) { ///< 如果已经存在头部节点的话
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    } else { ///< 如果不存在头部节点
        _head = _tail = node;
    }
}

///< 把节点移动到头部
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    if (_head == node) return;
    
    if (_tail == node) { ///< 如果需要移动的节点是尾部节点
        ///< node节点的上一个, 现在变成尾节点
        _tail = node->_prev;
        _tail->_next = nil;
    } else { ///< 如果需要移动的节点不是尾部节点
        node->_next->_prev = node->_prev; ///< node节点的下一个节点的上一个节点本来是node节点的, 现在指向node节点的上一个节点
        node->_prev->_next = node->_next; ///< node节点的上一个节点的下一个节点本来也是node节点的, 现在指向node节点的下一个节点
    }
    node->_next = _head; ///< node现在的下一个节点变成原来的头节点
    node->_prev = nil; ///< node的上一个节点指向nil
    _head->_prev = node; ///< 原来的头节点的上一个节点, 之前指向nil, 现在指向node节点
    _head = node; ///< 头部节点的指针指向node节点, 即现在node节点变成头部节点
}

///< 移除节点
- (void)removeNode:(_YYLinkedMapNode *)node {
    ///< 根据node的key, 从字典中移除node
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
    ///< 减去这个节点的开销, 每个开销是api传过来的,也就是程序员自己定义的
    _totalCost -= node->_cost;
    ///< 节点总数减1
    _totalCount--;
    ///< 如果要移除的node有下一个节点, 那么就将node节点的上一个节点赋值给下一个节点的上一个节点
    if (node->_next) node->_next->_prev = node->_prev;
    ///< 如果要移除的node有上一个节点, 那么就将node节点的下一个节点赋值给上一个节点的下一个节点
    if (node->_prev) node->_prev->_next = node->_next;
    
    ///< 如果node节点为头部节点, 那么将node节点的下一个节点赋值给头部节点
    if (_head == node) _head = node->_next;
    
    ///< 如果node节点为尾部节点, 那么将node节点的上一个节点赋值给尾部节点
    if (_tail == node) _tail = node->_prev;
}

///< 移除尾部节点, 有返回值
- (_YYLinkedMapNode *)removeTailNode {
    ///< 如果不存在尾部节点, 直接返回nil
    if (!_tail) return nil;
    _YYLinkedMapNode *tail = _tail;
    
    ///< 通过尾部节点的key值, 从字典中移除尾部节点
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
    
    ///< 总开销中减去尾部节点的开销
    _totalCost -= _tail->_cost;
    
    ///< 缓存的总节点数减去1
    _totalCount--;
    if (_head == _tail) { ///< 如果头部节点等于尾部节点, 那么说明链表中只有一个节点
        _head = _tail = nil; ///< 那么将头部节点和尾部节点都置空即可
    } else { ///< 表明链表中不止一个节点
        _tail = _tail->_prev; ///< 将尾部节点的上一个节点赋值给尾部节点
        _tail->_next = nil; ///< 尾部节点的下一个节点赋值为nil
    }
    
    ///< 此时返回的是移除的尾部节点的地址, 而现在的_tail的地址已经指向了当前的尾部节点的地址
    return tail;
}

///< 移除所有
- (void)removeAll {
    ///< 总开销为0
    _totalCost = 0;
    ///< 总节点数为0
    _totalCount = 0;
    
    ///< 头部节点和尾部节点都置空
    _head = nil;
    _tail = nil;
    
    
    if (CFDictionaryGetCount(_dic) > 0) {
        
        ///< 创建一个新的变量去接这个字典的地址
        CFMutableDictionaryRef holder = _dic;
        
        ///< 重新建一个可变字典赋值给_dic
        _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        
        ///< 异步释放
        ///< 把原来的_dic地址指向的内存空间释放掉了
        ///< _dic指向了新的地址
        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);
        }
    }
}

以上就是我的总结, 感谢大家!
YYCache的github地址

PS. 本人有若干成套学习视频, 包含Java, 数据结构与算法, iOS, 安卓, python, flutter等等, 如有需要, 联系微信tsaievan.

你可能感兴趣的:(YYMemoryCache的源码分析)