iOS 实现内存缓存

计算机为了提升访问数据的速度,实现了CPU的多级缓存机制,cpu计算时经常要用到的数据会被从其他地方读到cache里,但cache的大小是有限的,在缓存空间不够用时,就势必要逐出数据为新数据腾出空间... 由此引入了LRU策略.

一、实现内存缓存的思路

LRU(Least Recently use), 在空间不够时,将最近最少使用的数据删除来为新数据腾空间,为了实现这样的功能,需要思考的东西:

  1. 一份一份的数据需要按照时间排列,内存不足时从最老的一份开始删除,直到满足条件。这里令最新数据放在最前面,最老的数据放到最后面。
  2. 新数据读到内存时,需要插入到最前面;老数据再次被访问时,移动到最前面。
  3. 访问数据效率要快, 复杂度为O(1).
  4. 数据的写入、更改需要加锁。

综上所述,应该使用双向链表来实现这些数据的排列,使得在移动数据到头部时,时间复杂度O(1)。如果是单向链表,则在移动节点时,该节点的上一节点的next指向下一节点时, 上一节点不好访问。
同时为了使得在访问数据时复杂度O(1), 我们还需要使用哈希表我们在iOS中,使用字典。NSDictionary和NSSet的底层实现原理

二、具体实现代码

YYCache框架中的YYMemoryCache类就是根据LRU这个策略来实现的,具体代码可以参考之。
关键在于双向链表的节点设置双向链表的设计缓存类的实现 三部分对应了三个类_YYLinkedMapNode 、_YYLinkedMap、YYMemoryCache.

// 一、双向链表的节点设置
@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;
}
@end


// 二、双向链表的设计
@implementation _YYLinkedMap
- (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) {
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        node->_next->_prev = node->_prev;
        node->_prev->_next = node->_next;
    }
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}
- (_YYLinkedMapNode *)removeTailNode {
    if (!_tail) return nil;
    _YYLinkedMapNode *tail = _tail;
    CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
    _totalCost -= _tail->_cost;
    _totalCount--;
    if (_head == _tail) {
        _head = _tail = nil;
    } else {
        _tail = _tail->_prev;
        _tail->_next = nil;
    }
    return tail;
}
@end

// 三、缓存类的实现
@implementation YYMemoryCache
- (id)objectForKey:(id)key {
    if (!key) return nil;
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
        node->_time = CACurrentMediaTime();
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil;
}

- (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) {
        _lru->_totalCost -= node->_cost;
        _lru->_totalCost += cost;
        node->_cost = cost;
        node->_time = now;
        node->_value = object;
        [_lru bringNodeToHead:node];
    } else {
        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);
}
@end

在iOS中系统提供了NSCache这个类来实现内存缓存,为什么选择YYCaChe?

  • It uses LRU (least-recently-used) to remove objects; NSCache's eviction method is non-deterministic.
  • It can be controlled by cost, count and age; NSCache's limits are imprecise.
  • It can be configured to automatically evict objects when receive memory
    warning or app enter background.

你可能感兴趣的:(iOS 实现内存缓存)