From 431f91520d6a6bf7492fc5f71348491431ae4e98 Mon Sep 17 00:00:00 2001 From: ibireme Date: Tue, 14 Jun 2016 00:06:03 +0800 Subject: [PATCH] merge from YYCache --- YYWebImage/Cache/YYDiskCache.h | 4 + YYWebImage/Cache/YYDiskCache.m | 26 +++++++ YYWebImage/Cache/YYKVStorage.m | 126 ++++++++++++++++++------------- YYWebImage/Cache/YYMemoryCache.m | 10 +-- 4 files changed, 110 insertions(+), 56 deletions(-) diff --git a/YYWebImage/Cache/YYDiskCache.h b/YYWebImage/Cache/YYDiskCache.h index d61c621..dd193e4 100644 --- a/YYWebImage/Cache/YYDiskCache.h +++ b/YYWebImage/Cache/YYDiskCache.h @@ -131,6 +131,10 @@ NS_ASSUME_NONNULL_BEGIN */ @property NSTimeInterval autoTrimInterval; +/** + Set `YES` to enable error logs for debug. + */ +@property BOOL errorLogsEnabled; #pragma mark - Initializer ///============================================================================= diff --git a/YYWebImage/Cache/YYDiskCache.m b/YYWebImage/Cache/YYDiskCache.m index 77d18c8..735cc5d 100644 --- a/YYWebImage/Cache/YYDiskCache.m +++ b/YYWebImage/Cache/YYDiskCache.m @@ -11,6 +11,7 @@ #import "YYDiskCache.h" #import "YYKVStorage.h" +#import #import #import #import @@ -149,8 +150,18 @@ - (NSString *)_filenameForKey:(NSString *)key { return filename; } +- (void)_appWillBeTerminated { + Lock(); + _kv = nil; + Unlock(); +} + #pragma mark - public +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; +} + - (instancetype)init { @throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil]; return [self initWithPath:@"" inlineThreshold:0]; @@ -193,6 +204,8 @@ - (instancetype)initWithPath:(NSString *)path [self _trimRecursively]; _YYDiskCacheSetGlobal(self); + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil]; return self; } @@ -429,4 +442,17 @@ - (NSString *)description { else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path]; } +- (BOOL)errorLogsEnabled { + Lock(); + BOOL enabled = _kv.errorLogsEnabled; + Unlock(); + return enabled; +} + +- (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled { + Lock(); + _kv.errorLogsEnabled = errorLogsEnabled; + Unlock(); +} + @end diff --git a/YYWebImage/Cache/YYKVStorage.m b/YYWebImage/Cache/YYKVStorage.m index 5ea4e27..f3587b0 100644 --- a/YYWebImage/Cache/YYKVStorage.m +++ b/YYWebImage/Cache/YYKVStorage.m @@ -19,6 +19,9 @@ #import "sqlite3.h" #endif + +static const NSUInteger kMaxErrorRetryCount = 8; +static const NSTimeInterval kMinRetryTimeInterval = 2.0; static const int kPathLengthMax = PATH_MAX - 64; static NSString *const kDBFileName = @"manifest.sqlite"; static NSString *const kDBShmFileName = @"manifest.sqlite-shm"; @@ -26,7 +29,19 @@ static NSString *const kDataDirectoryName = @"data"; static NSString *const kTrashDirectoryName = @"trash"; + /* + File: + /path/ + /manifest.sqlite + /manifest.sqlite-shm + /manifest.sqlite-wal + /data/ + /e10adc3949ba59abbe56e057f20f883e + /e10adc3949ba59abbe56e057f20f883e + /trash/ + /unused_file_or_folder + SQL: create table if not exists manifest ( key text, @@ -41,6 +56,22 @@ primary key(key) create index if not exists last_access_time_idx on manifest(last_access_time); */ +/// Returns nil in App Extension. +static UIApplication *_YYSharedApplication() { + static BOOL isAppExtension = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class cls = NSClassFromString(@"UIApplication"); + if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES; + if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES; + }); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + return isAppExtension ? nil : [UIApplication performSelector:@selector(sharedApplication)]; +#pragma clang diagnostic pop +} + + @implementation YYKVStorageItem @end @@ -54,49 +85,40 @@ @implementation YYKVStorage { sqlite3 *_db; CFMutableDictionaryRef _dbStmtCache; - - BOOL _invalidated; ///< If YES, then the db should not open again, all read/write should be ignored. - BOOL _dbIsClosing; ///< If YES, then the db is during closing. + NSTimeInterval _dbLastOpenErrorTime; + NSUInteger _dbOpenErrorCount; } #pragma mark - db - (BOOL)_dbOpen { - BOOL shouldOpen = YES; - if (_invalidated) { - shouldOpen = NO; - } else if (_dbIsClosing) { - shouldOpen = NO; - } else if (_db){ - shouldOpen = NO; - } - if (!shouldOpen) return YES; + if (_db) return YES; int result = sqlite3_open(_dbPath.UTF8String, &_db); if (result == SQLITE_OK) { CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks; CFDictionaryValueCallBacks valueCallbacks = {0}; _dbStmtCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &keyCallbacks, &valueCallbacks); + _dbLastOpenErrorTime = 0; + _dbOpenErrorCount = 0; return YES; } else { - NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result); + _db = NULL; + if (_dbStmtCache) CFRelease(_dbStmtCache); + _dbStmtCache = NULL; + _dbLastOpenErrorTime = CACurrentMediaTime(); + _dbOpenErrorCount++; + + if (_errorLogsEnabled) { + NSLog(@"%s line:%d sqlite open failed (%d).", __FUNCTION__, __LINE__, result); + } return NO; } } - (BOOL)_dbClose { - BOOL needClose = YES; - if (!_db) { - needClose = NO; - } else if (_invalidated) { - needClose = NO; - } else if (_dbIsClosing) { - needClose = NO; - } else { - _dbIsClosing = YES; - } - if (!needClose) return YES; + if (!_db) return YES; int result = 0; BOOL retry = NO; @@ -118,16 +140,25 @@ - (BOOL)_dbClose { } } } else if (result != SQLITE_OK) { - NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result); + if (_errorLogsEnabled) { + NSLog(@"%s line:%d sqlite close failed (%d).", __FUNCTION__, __LINE__, result); + } } } while (retry); _db = NULL; - _dbIsClosing = NO; return YES; } -- (BOOL)_dbIsReady { - return (_db && !_dbIsClosing && !_invalidated); +- (BOOL)_dbCheck { + if (!_db) { + if (_dbOpenErrorCount < kMaxErrorRetryCount && + CACurrentMediaTime() - _dbLastOpenErrorTime > kMinRetryTimeInterval) { + return [self _dbOpen] && [self _dbInitialize]; + } else { + return NO; + } + } + return YES; } - (BOOL)_dbInitialize { @@ -136,14 +167,14 @@ - (BOOL)_dbInitialize { } - (void)_dbCheckpoint { - if (![self _dbIsReady]) return; + if (![self _dbCheck]) return; // Cause a checkpoint to occur, merge `sqlite-wal` file to `sqlite` file. sqlite3_wal_checkpoint(_db, NULL); } - (BOOL)_dbExecute:(NSString *)sql { if (sql.length == 0) return NO; - if (![self _dbIsReady]) return NO; + if (![self _dbCheck]) return NO; char *error = NULL; int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &error); @@ -156,7 +187,7 @@ - (BOOL)_dbExecute:(NSString *)sql { } - (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql { - if (![self _dbIsReady] || sql.length == 0 || !_dbStmtCache) return NULL; + if (![self _dbCheck] || 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); @@ -230,7 +261,7 @@ - (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key { } - (BOOL)_dbUpdateAccessTimeWithKeys:(NSArray *)keys { - if (![self _dbIsReady]) return NO; + if (![self _dbCheck]) return NO; int t = (int)time(NULL); NSString *sql = [NSString stringWithFormat:@"update manifest set last_access_time = %d where key in (%@);", t, [self _dbJoinedKeys:keys]]; @@ -266,7 +297,7 @@ - (BOOL)_dbDeleteItemWithKey:(NSString *)key { } - (BOOL)_dbDeleteItemWithKeys:(NSArray *)keys { - if (![self _dbIsReady]) return NO; + if (![self _dbCheck]) return NO; NSString *sql = [NSString stringWithFormat:@"delete from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; sqlite3_stmt *stmt = NULL; int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); @@ -353,7 +384,7 @@ - (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)e } - (NSMutableArray *)_dbGetItemWithKeys:(NSArray *)keys excludeInlineData:(BOOL)excludeInlineData { - if (![self _dbIsReady]) return nil; + if (![self _dbCheck]) return nil; NSString *sql; if (excludeInlineData) { sql = [NSString stringWithFormat:@"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; @@ -427,7 +458,7 @@ - (NSString *)_dbGetFilenameWithKey:(NSString *)key { } - (NSMutableArray *)_dbGetFilenameWithKeys:(NSArray *)keys { - if (![self _dbIsReady]) return nil; + if (![self _dbCheck]) return nil; NSString *sql = [NSString stringWithFormat:@"select filename from manifest where key in (%@);", [self _dbJoinedKeys:keys]]; sqlite3_stmt *stmt = NULL; int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL); @@ -510,8 +541,8 @@ - (NSMutableArray *)_dbGetFilenamesWithTimeEarlierThan:(int)time { return filenames; } -- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeDescWithLimit:(int)count { - NSString *sql = @"select key, filename, size from manifest order by last_access_time desc limit ?1;"; +- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count { + NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;"; sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; if (!stmt) return nil; sqlite3_bind_int(stmt, 1, count); @@ -580,26 +611,22 @@ - (int)_dbGetTotalItemCount { #pragma mark - file - (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data { - if (_invalidated) return NO; NSString *path = [_dataPath stringByAppendingPathComponent:filename]; return [data writeToFile:path atomically:NO]; } - (NSData *)_fileReadWithName:(NSString *)filename { - if (_invalidated) return nil; NSString *path = [_dataPath stringByAppendingPathComponent:filename]; NSData *data = [NSData dataWithContentsOfFile:path]; return data; } - (BOOL)_fileDeleteWithName:(NSString *)filename { - if (_invalidated) return NO; NSString *path = [_dataPath stringByAppendingPathComponent:filename]; return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; } - (BOOL)_fileMoveAllToTrash { - if (_invalidated) return NO; CFUUIDRef uuidRef = CFUUIDCreate(NULL); CFStringRef uuid = CFUUIDCreateString(NULL, uuidRef); CFRelease(uuidRef); @@ -613,7 +640,6 @@ - (BOOL)_fileMoveAllToTrash { } - (void)_fileEmptyTrashInBackground { - if (_invalidated) return; NSString *trashPath = _trashPath; dispatch_queue_t queue = _trashQueue; dispatch_async(queue, ^{ @@ -641,10 +667,6 @@ - (void)_reset { [self _fileEmptyTrashInBackground]; } -- (void)_appWillBeTerminated { - _invalidated = YES; -} - #pragma mark - public - (instancetype)init { @@ -698,13 +720,15 @@ - (instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type { return nil; } [self _fileEmptyTrashInBackground]; // empty the trash if failed at last time - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil]; return self; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; + UIBackgroundTaskIdentifier taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{}]; [self _dbClose]; + if (taskID != UIBackgroundTaskInvalid) { + [_YYSharedApplication() endBackgroundTask:taskID]; + } } - (BOOL)saveItem:(YYKVStorageItem *)item { @@ -841,7 +865,7 @@ - (BOOL)removeItemsToFitSize:(int)maxSize { BOOL suc = NO; do { int perCount = 16; - items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount]; + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxSize) { if (item.filename) { @@ -871,7 +895,7 @@ - (BOOL)removeItemsToFitCount:(int)maxCount { BOOL suc = NO; do { int perCount = 16; - items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount]; + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxCount) { if (item.filename) { @@ -909,7 +933,7 @@ - (void)removeAllItemsWithProgressBlock:(void(^)(int removedCount, int totalCoun NSArray *items = nil; BOOL suc = NO; do { - items = [self _dbGetItemSizeInfoOrderByTimeDescWithLimit:perCount]; + items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (left > 0) { if (item.filename) { diff --git a/YYWebImage/Cache/YYMemoryCache.m b/YYWebImage/Cache/YYMemoryCache.m index 5c078b2..9042d9b 100644 --- a/YYWebImage/Cache/YYMemoryCache.m +++ b/YYWebImage/Cache/YYMemoryCache.m @@ -360,16 +360,16 @@ - (NSUInteger)totalCost { return totalCost; } -- (BOOL)releaseInMainThread { +- (BOOL)releaseOnMainThread { pthread_mutex_lock(&_lock); - BOOL releaseInMainThread = _lru->_releaseOnMainThread; + BOOL releaseOnMainThread = _lru->_releaseOnMainThread; pthread_mutex_unlock(&_lock); - return releaseInMainThread; + return releaseOnMainThread; } -- (void)setReleaseInMainThread:(BOOL)releaseInMainThread { +- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread { pthread_mutex_lock(&_lock); - _lru->_releaseOnMainThread = releaseInMainThread; + _lru->_releaseOnMainThread = releaseOnMainThread; pthread_mutex_unlock(&_lock); }