iOSiOS原理专题IT@程序员猿媛

YYKit源码之YYCache

2019-04-22  本文已影响0人  _兜兜转转_

YYCache源码解读

大致思路是YYMemoryCache是使用的双向链表+CFDictionaryCreateMutable,查找复杂度O(1),大量使用信号量实现读写的安全机制;个人定制化强,不受系统影响;而且不限object的类型,为泛型id。优点多多

@interface YYMemoryCache : NSObject

#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================

//cache的name
@property (nullable, copy) NSString *name;

//cache的一共的个数 (read-only) 
@property (readonly) NSUInteger totalCount;

//cache的一共占用内存大小
@property (readonly) NSUInteger totalCost;


#pragma mark - Limit
///=============================================================================
/// @name Limit
///=============================================================================

//cache的个数
@property NSUInteger countLimit;

//cache的缓存大小空间
@property NSUInteger costLimit;


@property NSTimeInterval ageLimit;

//自动检查更新缓存的时间
@property NSTimeInterval autoTrimInterval;

//接收内存警告的时候 释放所有缓存 默认YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;

//进入到后台的时候 释放所有缓存 默认YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;

//接收内存警告的时候 执行的block
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);

//进入后台的时候 执行的block
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);

//是否在mainThread 释放 默认NO
@property BOOL releaseOnMainThread;

//是否异步释放缓存 默认YES
@property BOOL releaseAsynchronously;


#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================

//是否包含key
- (BOOL)containsObjectForKey:(id)key;

//返回该key的 obj
- (nullable id)objectForKey:(id)key;

//设置obj 
- (void)setObject:(nullable id)object forKey:(id)key;

//设置key为obj 占用内存大小
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;

//删除key的obj
- (void)removeObjectForKey:(id)key;

//删除所有obj
- (void)removeAllObjects;


#pragma mark - Trim
///=============================================================================
/// @name Trim
///=============================================================================

//设置更新缓存后的个数
- (void)trimToCount:(NSUInteger)count;

//设置更新缓存后的大小
- (void)trimToCost:(NSUInteger)cost;

//设置更新缓存的age的临界值
- (void)trimToAge:(NSTimeInterval)age;

@end

@end

关键函数注释获取内存大小和个数 没有循环遍历大小,直接返回值,因为都是动态计算的;

//信号量读数据加锁 使用CFDictionaryContainsKey c函数返回其值。
- (BOOL)containsObjectForKey:(id)key {
    if (!key) return NO;
    pthread_mutex_lock(&_lock);
    BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
    pthread_mutex_unlock(&_lock);
    return contains;
}
//先在CFDictionaryGetValue 获取node的地址
- (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();
//更新node的节点位置到head,删除的时候是在最后删除
        [_lru bringNodeToHead:node];
    }
    pthread_mutex_unlock(&_lock);
    return node ? node->_value : nil;
}

- (void)setObject:(id)object forKey:(id)key {
    [self setObject:object forKey:key withCost:0];
}

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
    if (!key) return;
    if (!object) {
        [self removeObjectForKey:key];
        return;
    }
//信号量加锁
    pthread_mutex_lock(&_lock);
//获取node
    _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;
//更新node到head
        [_lru bringNodeToHead:node];
    } else {
        node = [_YYLinkedMapNode new];
        node->_cost = cost;
        node->_time = now;
        node->_key = key;
        node->_value = object;
//更新node到head

        [_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 value
- (void)removeObjectForKey:(id)key {
    if (!key) return;
    pthread_mutex_lock(&_lock);
    _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
    if (node) {
//node 删除
        [_lru removeNode:node];
        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)removeAllObjects {
//删除all obj 加锁删除
    pthread_mutex_lock(&_lock);
    [_lru removeAll];
    pthread_mutex_unlock(&_lock);
}

YYDiskCache源码解读

@interface YYDiskCache : NSObject

#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================

/** cache的名字 */
@property (nullable, copy) NSString *name;

/** cache的路径 (read-only). */
@property (readonly) NSString *path;

/**
 SQLite的文件大小 默认20kb.
 */
@property (readonly) NSUInteger inlineThreshold;

/**
 没有实现`NSCoding` protocol的可以使用此block代替
 The default value is nil.
 */
@property (nullable, copy) NSData *(^customArchiveBlock)(id object);

/**
 没有实现`NSCoding` protocol的可以使用此block代替
 
 The default value is nil.
 */
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);

/**
cache的加密方式 默认是md5
 
 The default value is nil.
 */
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);



#pragma mark - Limit
///=============================================================================
/// @name Limit
///=============================================================================

/**
 The maximum number of objects the cache should hold.
 
 @discussion The default value is NSUIntegerMax, which means no limit.
 This is not a strict limit — if the cache goes over the limit, some objects in the
 cache could be evicted later in background queue.
 */
@property NSUInteger countLimit;

/**
 The maximum total cost that the cache can hold before it starts evicting objects.
 
 @discussion The default value is NSUIntegerMax, which means no limit.
 This is not a strict limit — if the cache goes over the limit, some objects in the
 cache could be evicted later in background queue.
 */
@property NSUInteger costLimit;

/**
 The maximum expiry time of objects in cache.
 
 @discussion The default value is DBL_MAX, which means no limit.
 This is not a strict limit — if an object goes over the limit, the objects could
 be evicted later in background queue.
 */
@property NSTimeInterval ageLimit;

//缓存应保留的最小空间
@property NSUInteger freeDiskSpaceLimit;

//更新缓存的频率 默认60s
@property NSTimeInterval autoTrimInterval;

//是否开启日期功能 在DEBUG环境下
@property BOOL errorLogsEnabled;

#pragma mark - Initializer
///=============================================================================
/// @name Initializer
///=============================================================================
//禁用init 和new函数
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

//创建一个基础的路径保存cache
- (nullable instancetype)initWithPath:(NSString *)path;
//保存完整的路径到文件,如果文件大于threshold
- (nullable instancetype)initWithPath:(NSString *)path
                      inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;


#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================

//是否包含key
- (BOOL)containsObjectForKey:(NSString *)key;
//是否包含该key
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;

- (nullable id<NSCoding>)objectForKey:(NSString *)key;

- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> _Nullable object))block;

//设置key 保存obj
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;

//如果 obj 是nil 直接删除key,
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block;

//删除该key
- (void)removeObjectForKey:(NSString *)key;

//删除该 key 
- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;

//删除allObjects
- (void)removeAllObjects;

//删除allObjects
- (void)removeAllObjectsWithBlock:(void(^)(void))block;

//删除obj 和回调block
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
                                 endBlock:(nullable void(^)(BOOL error))end;


//所有大小
- (NSInteger)totalCount;
- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;

//占用空间大小
- (NSInteger)totalCost;
- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block;


#pragma mark - Trim
///=============================================================================
/// @name Trim
///=============================================================================


- (void)trimToCount:(NSUInteger)count;


- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;


- (void)trimToCost:(NSUInteger)cost;


- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;


- (void)trimToAge:(NSTimeInterval)age;


- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;


#pragma mark - Extended Data
///=============================================================================
/// @name Extended Data
///=============================================================================


+ (nullable NSData *)getExtendedDataFromObject:(id)object;


+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;

@end

重要的是简历SQLite的初始化

create index if not exists last_access_time_idx on manifest(last_access_time) 使用last_access_time创建索引和primary key(key),缩短select的时间。
每次YYDisk读取的data都会加入到YYMemoryCache中,这个和runtime的method的cache原理一样的,下次访问该data的时候首先进行memory查找,没有的话再到disk中查找,增加memory的命中率,提高效率。

- (BOOL)_dbInitialize {
    NSString *sql = @"pragma journal_mode = wal; pragma synchronous = normal; 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)); create index if not exists last_access_time_idx on manifest(last_access_time);";
    return [self _dbExecute:sql];
}
上一篇 下一篇

猜你喜欢

热点阅读