> For the complete documentation index, see [llms.txt](https://philm.gitbook.io/philm-ios-wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://philm.gitbook.io/philm-ios-wiki/mei-zhou-yue-du/yycache-shen-ru-xue-xi.md).

# YYCache深入学习

## 简介

YYCahce 是YYkit组件库中的一部分，YYCache提供了内存缓存，和持久性的硬盘缓存。

## 一个合理缓存应该有的设计

* 合理的增删改查接口
* 高速缓存，提高常用缓存的返回性能和效率
* 低速缓存，磁盘大文件缓存
* 良好缓存限制策略
* 高性能，线程安全

## 基本设计思路

YYCache 提供对外的整合接口，YYMemoryCache 提供内存存储缓存，通过lru算法进行处理，YYDiskCache提供file和sqlite3的两种持久化存储方式

## YYMemoryCache

YYMemoryCahce 作为一个内存缓存，提供高速缓存，并且因为内存有限，需要进行一定的限制

1.线程安全

在频率高的并发数据操作中，必须保证线程安全，不然拿到的数据不符合预期，或者导致读写冲突 在YYMemoryCache中，采用的pthread\_mutex加锁

pthread\_mutex 定义了一组跨平台的线程相关的 API，pthread\_mutex 表示互斥锁。互斥锁的实现原理与信号量非常相似，不是使用忙等，而是阻塞线程并睡眠，需要进行上下文切换。

```
// 主要使用api
pthread_mutex_lock(&mutex); // 申请锁
// 临界区
pthread_mutex_unlock(&mutex); // 释放锁
```

pthread\_mutex支持递归锁，递归锁，就是在内部加锁调用的时候，递归了自身，这样子，会导致死锁。

* pthread\_mutex则支持递归处理 attr PTHREAD\_MUTEX\_RECURSIVE
* memory中使用pthread，在循环释放的时候，通过pthread\_mutex\_trylock实现简单的自旋锁

原文：OSSpinLock 和 dispatch\_semaphore 都不会产生特别明显的死锁，所以我也无法确定用 dispatch\_semaphore 代替 OSSpinLock 是否正确。能够肯定的是，用 pthread\_mutex 是安全的。

2.LRU

最近使用优先，也就是认为，最近使用的，最大可能性会再次用到 里面实现用到一个双向链表的结构进行处理，使用到的就会被移动到表头 在删除释放的时候，就会从队尾进行删除释放

3.键值对存储操作 通过创建链表节点node，进行间接操作数据

```
#pragma mark - _YYLinkedMapNode
@interface _YYLinkedMapNode : NSObject {
// 类似C中的private_extern，使用@private的话，限制太大，@package在类的镜像外进行引用就会报错
// 使用@public @protect等的话，就没什么限制的
// 目的是，限制在本文件中使用
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // 通过dic进行持有
__unsafe_unretained _YYLinkedMapNode *_next; // 通过dic进行持有
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
```

4.限制策略

```
// 限制条件有3个，age cost count ，一般情况下默认都不做限制
/// 个数限制
@property NSUInteger countLimit;
/// 过期时间
@property NSTimeInterval ageLimit;
/// 存储消耗限制，setObject的时候时候把内存大小的存进去
@property NSUInteger costLimit;

@property BOOL shouldRemoveAllObjectsWhenEnteringBackground; // 进入后台检查限制删除缓存，默认YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning; // 收到内存警告的时候
```

MemoryCache中使用的LRU的的算法进行删除缓存，当超过一定的限制的时候，会进行循环清理，也就是说，memory里面的东西并不是存进去就会在app的生命周期中一直存在的，可能会被释放掉。因此外部在使用的时候，需要判空处理的，如果为nil，则认为缓存已经失效，需要重新更新。

因此，YYMemoryCahce并不是数据在app的生命周期会被一直保留的，所以，在使用YYCahce最外层接口的时候，YYCache是会通过YYMemoryCache提供高速缓存，同时存入到低速缓存中。

获取的时候，YYMemoryCache获取不到，则会询问YYDiskCache。

5.某些小技巧，用于提供性能 会在一段时间，自行检查是否超过限制策略，超过则循环释放。 YYMemoryCache默认在进入后台的时候会进行检查。 因为一次释放太多，会导致资源消耗过大，因此，通过dispatch\_aync block的方式，放到后台线程释放

### YYDiskCache

1.线程安全 disk中使用dispatch\_semaphore信号量进行处理

dispatch\_semaphore 是信号量，但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时，它的性能比 pthread\_mutex 还要高，但一旦有等待情况出现时，性能就会下降许多。相对于 OSSpinLock 来说，它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说，它比较合适。

dispatch\_semaphore是属于闲等待，CPU不会消耗，因此，在做磁盘缓存的时候，用时较长，需要等待的话，会比较节省资源

OSSpinLock 自旋锁，不安全，所以使用了dispatch\_semaphore进行处理，因为大文件，需要等待的情况较多

2.大文件处理

* YYKVStorage 通过key value的一个封装，实现增删改查
* YYKVStorage 实现细节:

提供对应的sqlite3的db增删改查的接口，在操作的过程都做了防御式的操作

```
- (BOOL)_dbClose {
// 关闭数据库
if (!_db) {
return YES;
}

int result = 0;
BOOL retry = NO;
BOOL stmtFinalized = NO;

if (_dbStmtCache) {
_dbStmtCache = NULL;
}

do {
retry = NO;
result = sqlite3_close(_db);

if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
// 如果sqlite被占用，那进行stmt的析构操作，让其释放资源
// 在重新试一次
if (!stmtFinalized) {
stmtFinalized = YES; //析构
sqlite3_stmt *stmt;
// sqlite3_stmt 是一种辅助数据结构，用于操作二进制数据
// 循环释放所有的sqlite stmt
while ((stmt = sqlite3_next_stmt(_db, nil))) {
sqlite3_finalize(stmt); //析构 所有的sqlite stmt的辅助
retry = YES; // 然后重试，
}
}
} else if (result != SQLITE_OK) {
// 关闭失败
// 输出 错误日志
NSLog(@"关闭失败");
}

} while (retry);

_db = NULL;
return YES;

}

- (BOOL)_dbCheck {
if (_db) {
// 如果重试错误的次数小于限定次数，并且大于最小重试时间，则重新进行打开和初始化检查
if (_dbOpenErrorCount < kMaxErrorRetryCount &&
CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) {
return [self _dbOpen] && [self _dbInitialize];
} else {
return NO;
}
}
return YES; // 正常
}
```

对sqlite的stmt也做了缓存，加速其性能

```
// 获取缓存中的stmt，用sql做key
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));

if (!stmt) {
// 如果stmt为空的话
int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
if (result != SQLITE_OK) {
// 如果不成功
NSLog(@"创建stmt失败");
return NULL; // 返回空
}
CFDictionarySetValue(_dbStmtCache, sql.UTF8String, stmt); // 缓存起来，都是同一个stmt，每次创建的话，会大量消耗资源，因此这里做了缓存
} else {
sqlite3_reset(stmt);
}

return stmt;
}
```

存储方式有三个模式

1.文件 2.sqlite3 3.混合 // 混合的情况有个阈值，大于阈值，则存文件

* db sqlite3的使用细节

sqlite3\_stmt 操作数据的辅助数据接口，用于执行sql，并且返回结果 stmt 也进行做了缓存，因为这个sql会重复不定期使用

* 优化: 可以升级最新版本的sqlite3，以此提高效率

## YYCache

提供了增删改查的API，底下调用的YYMemoryCache和YYDiskCache，封装了一些基本逻辑。在save的时候，会保存到YYMemoryCache和YYDiskCache，读取的时候，会优先读取YYMemoryCache实现高速缓存，再读取低速缓存。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://philm.gitbook.io/philm-ios-wiki/mei-zhou-yue-du/yycache-shen-ru-xue-xi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
