YYCache Notes
- Basic Information
- Global Note
- File Notes
- 0. YYMemoryCache.h
- 1. YYMemoryCache.m
- 2. YYMemoryCache.m
- 3. YYMemoryCache.m
- 4. YYMemoryCache.m
- 5. YYMemoryCache.m
- 6. YYMemoryCache.m
- 7. YYMemoryCache.m
- 8. YYMemoryCache.m
- 9. YYMemoryCache.m
- 10. YYMemoryCache.m
- 11. YYMemoryCache.m
- 12. YYMemoryCache.m
- 13. YYMemoryCache.m
- 14. YYMemoryCache.m
- 15. YYMemoryCache.m
- 16. YYMemoryCache.m
- 17. YYDiskCache.h
- 18. YYDiskCache.h
- 19. YYDiskCache.m
- 20. YYDiskCache.m
- 21. YYDiskCache.m
- 22. YYDiskCache.m
- 23. YYKVStorage.m
- 24. YYKVStorage.m
- 25. YYKVStorage.m
- 26. YYKVStorage.m
- 27. YYKVStorage.m
- 28. YYKVStorage.m
- 29. YYKVStorage.m
- 30. YYKVStorage.m
- 31. YYKVStorage.m
- 32. YYKVStorage.m
- 33. YYKVStorage.m
- 34. YYKVStorage.m
- 35. YYKVStorage.m
- Summarize
Basic Information
- Name : YYCache
- Site : https://github.com/ibireme/YYCache
- Repo : https://github.com/ibireme/YYCache
- Revision : f433c3455121bd0308cd6f551613c7ec629e937a
- Description : A cache library with good performance on both memory and disk.
This is the author’s design thinking introduction: http://blog.ibireme.com/2015/10/26/yycache/
Global Note
The author’s growth experience is worth learning from, improved very quickly in over a year of iOS development.
File Notes
0. YYMemoryCache.h
- Path : /YYCache/YYMemoryCache.h
- Line : 16 - 30
- Note :
/**
YYMemoryCache is a fast in-memory cache that stores key-value pairs.
In contrast to NSDictionary, keys are retained and not copied.
The API and performance is similar to `NSCache`, all methods are thread-safe.
YYMemoryCache objects differ from NSCache in a few ways:
* 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.
The time of `Access Methods` in YYMemoryCache is typically in constant time (O(1)).
*/
- YYMemoryCache is memory cache.
- Keys are retained, not copied.
- Uses LRU (Least Recently Used) algorithm.
1. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 15 - 15
- Note :
#import <QuartzCore/QuartzCore.h>
QuartzCore framework needs research. #TODO#
2. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 18 - 18
- Note :
#if __has_include("YYDispatchQueuePool.h")
Can check if a header file is included this way.
3. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 32 - 45
- Note :
/**
A node in linked map.
Typically, you should not use this class directly.
*/
@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
Doubly linked list node. cost and time (last access time). _prev and _next are __unsafe_unretained, retained by outer CFMutableDictionaryRef.
@package modifier, accessible within framework, not accessible outside framework.
4. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 57 - 66
- Note :
@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;
}
Doubly linked list definition. Head, tail. dic used to store elements, also retains elements.
5. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 92 - 92
- Note :
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFMutableDictionaryRef usage mark.
6. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 101 - 154
- Note :
- (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;
}
- (void)removeNode:(_YYLinkedMapNode *)node {
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
_totalCost -= node->_cost;
_totalCount--;
if (node->_next) node->_next->_prev = node->_prev;
if (node->_prev) node->_prev->_next = node->_next;
if (_head == node) _head = node->_next;
if (_tail == node) _tail = node->_prev;
}
- (_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;
}
Basic operations of doubly linked list. Haven’t looked at algorithms for a long time. Posting it.
7. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 190 - 198
- Note :
- (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];
});
}
Simple timer.
8. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 208 - 218
- Note :
- (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;
Old version used OSSpinLock here, now changed to pthread_mutex_lock. Reason see author’s article, http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/
9. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 220 - 233
- Note :
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
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
}
}
Here trylock, when cannot acquire lock, usleep.
10. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 344 - 345
- Note :
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
Listen for entering background or memory warning
11. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 406 - 416
- Note :
- (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;
}
Note the lock. And set the latest access time for accessed element. And put this element at the head of the doubly linked list. Time complexity here is all constant.
Learned a function to get time CACurrentMediaTime().
12. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 478 - 478
- Note :
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
pthread_main_np checks main thread. Returns non-zero if main thread.
13. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 357 - 363
- Note :
- (NSUInteger)totalCount {
pthread_mutex_lock(&_lock);
NSUInteger count = _lru->_totalCount;
pthread_mutex_unlock(&_lock);
return count;
}
pthread_mutex_init(&_lock, NULL);
pthread_mutex_destroy(&_lock); pthread mutex basic usage.
14. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 291 - 291
- Note :
if (pthread_mutex_trylock(&_lock) == 0) {
Use trylock to avoid blocking
15. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 352 - 353
- Note :
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
Listen for app entering background, and system memory shortage.
16. YYMemoryCache.m
- Path : /YYCache/YYMemoryCache.m
- Line : 27 - 27
- Note :
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
static inline within file, inline
17. YYDiskCache.h
- Path : /YYCache/YYDiskCache.h
- Line : 44 - 53
- Note :
/**
If the object's data size (in bytes) is larger than this value, then object will
be stored as a file, otherwise the object will be stored in sqlite.
0 means all objects will be stored as separated files, NSUIntegerMax means all
objects will be stored in sqlite.
The default value is 20480 (20KB).
*/
@property (readonly) NSUInteger inlineThreshold;
YYDiskCache will check this threshold, below 20k will be stored in sqlite, otherwise stored as files.
18. YYDiskCache.h
- Path : /YYCache/YYDiskCache.h
- Line : 139 - 140
- Note :
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
Declare unavailable methods
19. YYDiskCache.m
- Path : /YYCache/YYDiskCache.m
- Line : 18 - 19
- Note :
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)
Semaphore
20. YYDiskCache.m
- Path : /YYCache/YYDiskCache.m
- Line : 48 - 58
- Note :
/// weak reference for all instances
static NSMapTable *_globalInstances;
static dispatch_semaphore_t _globalInstancesLock;
static void _YYDiskCacheInitGlobal() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_globalInstancesLock = dispatch_semaphore_create(1);
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
});
}
default is weak
The NSMapTable class is a mutable collection modeled after NSDictionary, with the following differences: Overview The major option is to have keys and/or values held “weakly” in a manner that entries are removed when one of the objects is reclaimed. Its keys or values may be copied on input or may use pointer identity for equality and hashing. It can contain arbitrary pointers (its contents are not constrained to being objects). You can configure an NSMapTable instance to operate on arbitrary pointers and not just objects, although typically you are encouraged to use the C function API for void * pointers. (See Managing Map Tables for more information) The object-based API (such as setObject:forKey:) will not work for non-object pointers without type-casting.
21. YYDiskCache.m
- Path : /YYCache/YYDiskCache.m
- Line : 85 - 93
- Note :
- (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];
});
}
First weak then strong. Can directly override outer self inside, convenient to use.
22. YYDiskCache.m
- Path : /YYCache/YYDiskCache.m
- Line : 155 - 155
- Note :
@throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil];
Actively throw exception
23. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 23 - 27
- Note :
static NSString *const kDBFileName = @"manifest.sqlite";
static NSString *const kDBShmFileName = @"manifest.sqlite-shm";
static NSString *const kDBWalFileName = @"manifest.sqlite-wal";
static NSString *const kDataDirectoryName = @"data";
static NSString *const kTrashDirectoryName = @"trash";
Don’t know about shm and wal. (Looks like I’ve only used sqlite before, but haven’t studied it carefully)
24. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 29 - 42
- Note :
/*
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)
);
create index if not exists last_access_time_idx on manifest(last_access_time);
*/
create index if not exists last_access_time_idx on manifest(last_access_time);
Create index (did C++ for 5 years never used this)
primary key(key) can specify sqlite type this way later
25. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 83 - 83
- Note :
NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result);
%s FUNCTION %d LINE
26. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 78 - 80
- Note :
CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks;
CFDictionaryValueCallBacks valueCallBacks = {0};
_dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallBacks);
CFDictionaryCreateMutable usage. Core Foundation usage.
27. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 111 - 119
- Note :
if (result == SQLITE_BUSY || result == SQLITE_LOCKED) {
if (!stmtFinalized) {
stmtFinalized = YES;
sqlite3_stmt *stmt;
while ((stmt = sqlite3_next_stmt(_db, nil)) != 0) {
sqlite3_finalize(stmt);
retry = YES;
}
}
When busy and locked, finalize all statements
28. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 138 - 142
- Note :
- (void)_dbCheckpoint {
if (![self _dbIsReady]) return;
// Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file.
sqlite3_wal_checkpoint(_db, NULL);
}
wal checkpoint
29. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 158 - 172
- Note :
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbIsReady] || sql.length == 0 || !_dbStmtCache) return NULL;
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
if (!stmt) {
int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
if (result != SQLITE_OK) {
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NULL;
}
CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
} else {
sqlite3_reset(stmt);
}
return stmt;
}
sqlite3_stmt cached by sql
30. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 192 - 216
- Note :
- (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
int timestamp = (int)time(NULL);
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
sqlite3_bind_int(stmt, 3, (int)value.length);
if (fileName.length == 0) {
sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
} else {
sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
}
sqlite3_bind_int(stmt, 5, timestamp);
sqlite3_bind_int(stmt, 6, timestamp);
sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0);
int result = sqlite3_step(stmt);
if (result != SQLITE_DONE) {
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NO;
}
return YES;
}
(?1, ?2, ?3, ?4, ?5, ?6, ?7) Question mark can have numbers after it…
31. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 218 - 218
- Note :
- (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key {
Update single access time
32. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 232 - 232
- Note :
- (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys {
Update multiple keys’ access time
33. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 603 - 606
- Note :
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
NSString *tmpPath = [_trashPath stringByAppendingPathComponent:(__bridge NSString *)(uuid)];
Generate uuid
34. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 615 - 627
- Note :
- (void)_fileEmptyTrashInBackground {
if (_invalidated) return;
NSString *trashPath = _trashPath;
dispatch_queue_t queue = _trashQueue;
dispatch_async(queue, ^{
NSFileManager *manager = [NSFileManager new];
NSArray *directoryContents = [manager contentsOfDirectoryAtPath:trashPath error:NULL];
for (NSString *path in directoryContents) {
NSString *fullPath = [trashPath stringByAppendingPathComponent:path];
[manager removeItemAtPath:fullPath error:NULL];
}
});
}
Here creates a new FileManager. Here doesn’t set delegate or anything. ?? Why create a new FileManager?
35. YYKVStorage.m
- Path : /YYCache/YYKVStorage.m
- Line : 831 - 860
- Note :
- (BOOL)removeItemsToFitSize:(int)maxSize {
if (maxSize == INT_MAX) return YES;
if (maxSize <= 0) return [self removeAllItems];
int total = [self _dbGetTotalItemSize];
if (total < 0) return NO;
if (total <= maxSize) return YES;
NSArray *items = nil;
BOOL suc = NO;
do {
int perCount = 16;
items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount];
for (YYKVStorageItem *item in items) {
if (total > maxSize) {
if (item.filename) {
[self _fileDeleteWithName:item.filename];
}
suc = [self _dbDeleteItemWithKey:item.key];
total -= item.size;
} else {
break;
}
if (!suc) break;
}
} while (total > maxSize && items.count > 0 && suc);
if (suc) [self _dbCheckpoint];
return suc;
}
- First get total size
- Each time get 16 least frequently accessed data
- Delete one by one (file or database record), check if remaining size meets target size
PS : Desc here is a bug. Should be Asc. See: https://github.com/ibireme/YYCache/issues/37
Summarize
Generated by XSourceNote at 2016-03-27 18:18:04 +0000