#import "TMDiskCache.h" #define TMDiskCacheError(error) if (error) { NSLog(@"%@ (%d) ERROR: %@", \ [[NSString stringWithUTF8String:__FILE__] lastPathComponent], \ __LINE__, [error localizedDescription]); } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 #define TMCacheStartBackgroundTask() UIBackgroundTaskIdentifier taskID = UIBackgroundTaskInvalid; \ taskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ \ [[UIApplication sharedApplication] endBackgroundTask:taskID]; }]; #define TMCacheEndBackgroundTask() [[UIApplication sharedApplication] endBackgroundTask:taskID]; #else #define TMCacheStartBackgroundTask() #define TMCacheEndBackgroundTask() #endif NSString * const TMDiskCachePrefix = @"com.tumblr.TMDiskCache"; NSString * const TMDiskCacheSharedName = @"TMDiskCacheShared"; @interface TMDiskCache () @property (assign) NSUInteger byteCount; @property (strong, nonatomic) NSURL *cacheURL; @property (assign, nonatomic) dispatch_queue_t queue; @property (strong, nonatomic) NSMutableDictionary *dates; @property (strong, nonatomic) NSMutableDictionary *sizes; @end @implementation TMDiskCache @synthesize willAddObjectBlock = _willAddObjectBlock; @synthesize willRemoveObjectBlock = _willRemoveObjectBlock; @synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock; @synthesize didAddObjectBlock = _didAddObjectBlock; @synthesize didRemoveObjectBlock = _didRemoveObjectBlock; @synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock; @synthesize byteLimit = _byteLimit; @synthesize ageLimit = _ageLimit; #pragma mark - Initialization - - (instancetype)initWithName:(NSString *)name { return [self initWithName:name rootPath:[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]]; } - (instancetype)initWithName:(NSString *)name rootPath:(NSString *)rootPath { if (!name) return nil; if (self = [super init]) { _name = [name copy]; _queue = [TMDiskCache sharedQueue]; _willAddObjectBlock = nil; _willRemoveObjectBlock = nil; _willRemoveAllObjectsBlock = nil; _didAddObjectBlock = nil; _didRemoveObjectBlock = nil; _didRemoveAllObjectsBlock = nil; _byteCount = 0; _byteLimit = 0; _ageLimit = 0.0; _dates = [[NSMutableDictionary alloc] init]; _sizes = [[NSMutableDictionary alloc] init]; NSString *pathComponent = [[NSString alloc] initWithFormat:@"%@.%@", TMDiskCachePrefix, _name]; _cacheURL = [NSURL fileURLWithPathComponents:@[ rootPath, pathComponent ]]; __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; [strongSelf createCacheDirectory]; [strongSelf initializeDiskProperties]; }); } return self; } - (NSString *)description { return [[NSString alloc] initWithFormat:@"%@.%@.%p", TMDiskCachePrefix, _name, self]; } + (instancetype)sharedCache { static id cache; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ cache = [[self alloc] initWithName:TMDiskCacheSharedName]; }); return cache; } + (dispatch_queue_t)sharedQueue { static dispatch_queue_t queue; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ queue = dispatch_queue_create([TMDiskCachePrefix UTF8String], DISPATCH_QUEUE_SERIAL); }); return queue; } #pragma mark - Private Methods - - (NSURL *)encodedFileURLForKey:(NSString *)key { if (![key length]) return nil; return [_cacheURL URLByAppendingPathComponent:[self encodedString:key]]; } - (NSString *)keyForEncodedFileURL:(NSURL *)url { NSString *fileName = [url lastPathComponent]; if (!fileName) return nil; return [self decodedString:fileName]; } - (NSString *)encodedString:(NSString *)string { if (![string length]) return @""; CFStringRef static const charsToEscape = CFSTR(".:/"); CFStringRef escapedString = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, charsToEscape, kCFStringEncodingUTF8); return (__bridge_transfer NSString *)escapedString; } - (NSString *)decodedString:(NSString *)string { if (![string length]) return @""; CFStringRef unescapedString = CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (__bridge CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8); return (__bridge_transfer NSString *)unescapedString; } #pragma mark - Private Trash Methods - + (dispatch_queue_t)sharedTrashQueue { static dispatch_queue_t trashQueue; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ NSString *queueName = [[NSString alloc] initWithFormat:@"%@.trash", TMDiskCachePrefix]; trashQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(trashQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); }); return trashQueue; } + (NSURL *)sharedTrashURL { static NSURL *sharedTrashURL; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ sharedTrashURL = [[[NSURL alloc] initFileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:TMDiskCachePrefix isDirectory:YES]; dispatch_async([self sharedTrashQueue], ^{ if (![[NSFileManager defaultManager] fileExistsAtPath:[sharedTrashURL path]]) { NSError *error = nil; [[NSFileManager defaultManager] createDirectoryAtURL:sharedTrashURL withIntermediateDirectories:YES attributes:nil error:&error]; TMDiskCacheError(error); } }); }); return sharedTrashURL; } +(BOOL)moveItemAtURLToTrash:(NSURL *)itemURL { if (![[NSFileManager defaultManager] fileExistsAtPath:[itemURL path]]) return NO; NSError *error = nil; NSString *uniqueString = [[NSProcessInfo processInfo] globallyUniqueString]; NSURL *uniqueTrashURL = [[TMDiskCache sharedTrashURL] URLByAppendingPathComponent:uniqueString]; BOOL moved = [[NSFileManager defaultManager] moveItemAtURL:itemURL toURL:uniqueTrashURL error:&error]; TMDiskCacheError(error); return moved; } + (void)emptyTrash { TMCacheStartBackgroundTask(); dispatch_async([self sharedTrashQueue], ^{ NSError *error = nil; NSArray *trashedItems = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[self sharedTrashURL] includingPropertiesForKeys:nil options:0 error:&error]; TMDiskCacheError(error); for (NSURL *trashedItemURL in trashedItems) { NSError *error = nil; [[NSFileManager defaultManager] removeItemAtURL:trashedItemURL error:&error]; TMDiskCacheError(error); } TMCacheEndBackgroundTask(); }); } #pragma mark - Private Queue Methods - - (BOOL)createCacheDirectory { if ([[NSFileManager defaultManager] fileExistsAtPath:[_cacheURL path]]) return NO; NSError *error = nil; BOOL success = [[NSFileManager defaultManager] createDirectoryAtURL:_cacheURL withIntermediateDirectories:YES attributes:nil error:&error]; TMDiskCacheError(error); return success; } - (void)initializeDiskProperties { NSUInteger byteCount = 0; NSArray *keys = @[ NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey ]; NSError *error = nil; NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:_cacheURL includingPropertiesForKeys:keys options:NSDirectoryEnumerationSkipsHiddenFiles error:&error]; TMDiskCacheError(error); for (NSURL *fileURL in files) { NSString *key = [self keyForEncodedFileURL:fileURL]; error = nil; NSDictionary *dictionary = [fileURL resourceValuesForKeys:keys error:&error]; TMDiskCacheError(error); NSDate *date = [dictionary objectForKey:NSURLContentModificationDateKey]; if (date) [_dates setObject:date forKey:key]; NSNumber *fileSize = [dictionary objectForKey:NSURLTotalFileAllocatedSizeKey]; if (fileSize) { [_sizes setObject:fileSize forKey:key]; byteCount += [fileSize unsignedIntegerValue]; } } if (byteCount > 0) self.byteCount = byteCount; // atomic } - (BOOL)setFileModificationDate:(NSDate *)date forURL:(NSURL *)fileURL { NSError *error = nil; BOOL success = [[NSFileManager defaultManager] setAttributes:@{ NSFileModificationDate: date } ofItemAtPath:[fileURL path] error:&error]; TMDiskCacheError(error); if (success) [_dates setObject:date forKey:[self keyForEncodedFileURL:fileURL]]; return success; } - (BOOL)removeFileAndExecuteBlocksForKey:(NSString *)key { NSURL *fileURL = [self encodedFileURLForKey:key]; if (!fileURL || ![[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) return NO; if (_willRemoveObjectBlock) _willRemoveObjectBlock(self, key, nil, fileURL); BOOL trashed = [TMDiskCache moveItemAtURLToTrash:fileURL]; if (!trashed) return NO; [TMDiskCache emptyTrash]; NSNumber *byteSize = [_sizes objectForKey:key]; if (byteSize) self.byteCount = _byteCount - [byteSize unsignedIntegerValue]; // atomic [_sizes removeObjectForKey:key]; [_dates removeObjectForKey:key]; if (_didRemoveObjectBlock) _didRemoveObjectBlock(self, key, nil, fileURL); return YES; } - (void)trimDiskToSize:(NSUInteger)trimByteCount { if (_byteCount <= trimByteCount) return; NSArray *keysSortedBySize = [_sizes keysSortedByValueUsingSelector:@selector(compare:)]; for (NSString *key in [keysSortedBySize reverseObjectEnumerator]) { // largest objects first [self removeFileAndExecuteBlocksForKey:key]; if (_byteCount <= trimByteCount) break; } } - (void)trimDiskToSizeByDate:(NSUInteger)trimByteCount { if (_byteCount <= trimByteCount) return; NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)]; for (NSString *key in keysSortedByDate) { // oldest objects first [self removeFileAndExecuteBlocksForKey:key]; if (_byteCount <= trimByteCount) break; } } - (void)trimDiskToDate:(NSDate *)trimDate { NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)]; for (NSString *key in keysSortedByDate) { // oldest files first NSDate *accessDate = [_dates objectForKey:key]; if (!accessDate) continue; if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date [self removeFileAndExecuteBlocksForKey:key]; } else { break; } } } - (void)trimToAgeLimitRecursively { if (_ageLimit == 0.0) return; NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-_ageLimit]; [self trimDiskToDate:date]; __weak TMDiskCache *weakSelf = self; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_ageLimit * NSEC_PER_SEC)); dispatch_after(time, _queue, ^(void) { TMDiskCache *strongSelf = weakSelf; [strongSelf trimToAgeLimitRecursively]; }); } #pragma mark - Public Asynchronous Methods - - (void)objectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block { NSDate *now = [[NSDate alloc] init]; if (!key || !block) return; __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; NSURL *fileURL = [strongSelf encodedFileURLForKey:key]; id object = nil; if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { object = [NSKeyedUnarchiver unarchiveObjectWithFile:[fileURL path]]; [strongSelf setFileModificationDate:now forURL:fileURL]; } block(strongSelf, key, object, fileURL); }); } - (void)fileURLForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block { NSDate *now = [[NSDate alloc] init]; if (!key || !block) return; __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; NSURL *fileURL = [strongSelf encodedFileURLForKey:key]; if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]) { [strongSelf setFileModificationDate:now forURL:fileURL]; } else { fileURL = nil; } block(strongSelf, key, nil, fileURL); }); } - (void)setObject:(id )object forKey:(NSString *)key block:(TMDiskCacheObjectBlock)block { NSDate *now = [[NSDate alloc] init]; if (!key || !object) return; TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } NSURL *fileURL = [strongSelf encodedFileURLForKey:key]; if (strongSelf->_willAddObjectBlock) strongSelf->_willAddObjectBlock(strongSelf, key, object, fileURL); BOOL written = [NSKeyedArchiver archiveRootObject:object toFile:[fileURL path]]; if (written) { [strongSelf setFileModificationDate:now forURL:fileURL]; NSError *error = nil; NSDictionary *values = [fileURL resourceValuesForKeys:@[ NSURLTotalFileAllocatedSizeKey ] error:&error]; TMDiskCacheError(error); NSNumber *diskFileSize = [values objectForKey:NSURLTotalFileAllocatedSizeKey]; if (diskFileSize) { [strongSelf->_sizes setObject:diskFileSize forKey:key]; strongSelf.byteCount = strongSelf->_byteCount + [diskFileSize unsignedIntegerValue]; // atomic } if (strongSelf->_byteLimit > 0 && strongSelf->_byteCount > strongSelf->_byteLimit) [strongSelf trimToSizeByDate:strongSelf->_byteLimit block:nil]; } else { fileURL = nil; } if (strongSelf->_didAddObjectBlock) strongSelf->_didAddObjectBlock(strongSelf, key, object, written ? fileURL : nil); if (block) block(strongSelf, key, object, fileURL); TMCacheEndBackgroundTask(); }); } - (void)removeObjectForKey:(NSString *)key block:(TMDiskCacheObjectBlock)block { if (!key) return; TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } NSURL *fileURL = [strongSelf encodedFileURLForKey:key]; [strongSelf removeFileAndExecuteBlocksForKey:key]; if (block) block(strongSelf, key, nil, fileURL); TMCacheEndBackgroundTask(); }); } - (void)trimToSize:(NSUInteger)trimByteCount block:(TMDiskCacheBlock)block { if (trimByteCount == 0) { [self removeAllObjects:block]; return; } TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } [strongSelf trimDiskToSize:trimByteCount]; if (block) block(strongSelf); TMCacheEndBackgroundTask(); }); } - (void)trimToDate:(NSDate *)trimDate block:(TMDiskCacheBlock)block { if (!trimDate) return; if ([trimDate isEqualToDate:[NSDate distantPast]]) { [self removeAllObjects:block]; return; } TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } [strongSelf trimDiskToDate:trimDate]; if (block) block(strongSelf); TMCacheEndBackgroundTask(); }); } - (void)trimToSizeByDate:(NSUInteger)trimByteCount block:(TMDiskCacheBlock)block { if (trimByteCount == 0) { [self removeAllObjects:block]; return; } TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } [strongSelf trimDiskToSizeByDate:trimByteCount]; if (block) block(strongSelf); TMCacheEndBackgroundTask(); }); } - (void)removeAllObjects:(TMDiskCacheBlock)block { TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } if (strongSelf->_willRemoveAllObjectsBlock) strongSelf->_willRemoveAllObjectsBlock(strongSelf); [TMDiskCache moveItemAtURLToTrash:strongSelf->_cacheURL]; [TMDiskCache emptyTrash]; [strongSelf createCacheDirectory]; [strongSelf->_dates removeAllObjects]; [strongSelf->_sizes removeAllObjects]; strongSelf.byteCount = 0; // atomic if (strongSelf->_didRemoveAllObjectsBlock) strongSelf->_didRemoveAllObjectsBlock(strongSelf); if (block) block(strongSelf); TMCacheEndBackgroundTask(); }); } - (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block completionBlock:(TMDiskCacheBlock)completionBlock { if (!block) return; TMCacheStartBackgroundTask(); __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) { TMCacheEndBackgroundTask(); return; } NSArray *keysSortedByDate = [strongSelf->_dates keysSortedByValueUsingSelector:@selector(compare:)]; for (NSString *key in keysSortedByDate) { NSURL *fileURL = [strongSelf encodedFileURLForKey:key]; block(strongSelf, key, nil, fileURL); } if (completionBlock) completionBlock(strongSelf); TMCacheEndBackgroundTask(); }); } #pragma mark - Public Synchronous Methods - - (id )objectForKey:(NSString *)key { if (!key) return nil; __block id objectForKey = nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self objectForKey:key block:^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL) { objectForKey = object; dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif return objectForKey; } - (NSURL *)fileURLForKey:(NSString *)key { if (!key) return nil; __block NSURL *fileURLForKey = nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self fileURLForKey:key block:^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL) { fileURLForKey = fileURL; dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif return fileURLForKey; } - (void)setObject:(id )object forKey:(NSString *)key { if (!object || !key) return; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self setObject:object forKey:key block:^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)removeObjectForKey:(NSString *)key { if (!key) return; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self removeObjectForKey:key block:^(TMDiskCache *cache, NSString *key, id object, NSURL *fileURL) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)trimToSize:(NSUInteger)byteCount { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self trimToSize:byteCount block:^(TMDiskCache *cache) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)trimToDate:(NSDate *)date { if (!date) return; if ([date isEqualToDate:[NSDate distantPast]]) { [self removeAllObjects]; return; } dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self trimToDate:date block:^(TMDiskCache *cache) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)trimToSizeByDate:(NSUInteger)byteCount { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self trimToSizeByDate:byteCount block:^(TMDiskCache *cache) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)removeAllObjects { dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self removeAllObjects:^(TMDiskCache *cache) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } - (void)enumerateObjectsWithBlock:(TMDiskCacheObjectBlock)block { if (!block) return; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self enumerateObjectsWithBlock:block completionBlock:^(TMDiskCache *cache) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif } #pragma mark - Public Thread Safe Accessors - - (TMDiskCacheObjectBlock)willAddObjectBlock { __block TMDiskCacheObjectBlock block = nil; dispatch_sync(_queue, ^{ block = _willAddObjectBlock; }); return block; } - (void)setWillAddObjectBlock:(TMDiskCacheObjectBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_willAddObjectBlock = [block copy]; }); } - (TMDiskCacheObjectBlock)willRemoveObjectBlock { __block TMDiskCacheObjectBlock block = nil; dispatch_sync(_queue, ^{ block = _willRemoveObjectBlock; }); return block; } - (void)setWillRemoveObjectBlock:(TMDiskCacheObjectBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_willRemoveObjectBlock = [block copy]; }); } - (TMDiskCacheBlock)willRemoveAllObjectsBlock { __block TMDiskCacheBlock block = nil; dispatch_sync(_queue, ^{ block = _willRemoveAllObjectsBlock; }); return block; } - (void)setWillRemoveAllObjectsBlock:(TMDiskCacheBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_willRemoveAllObjectsBlock = [block copy]; }); } - (TMDiskCacheObjectBlock)didAddObjectBlock { __block TMDiskCacheObjectBlock block = nil; dispatch_sync(_queue, ^{ block = _didAddObjectBlock; }); return block; } - (void)setDidAddObjectBlock:(TMDiskCacheObjectBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_didAddObjectBlock = [block copy]; }); } - (TMDiskCacheObjectBlock)didRemoveObjectBlock { __block TMDiskCacheObjectBlock block = nil; dispatch_sync(_queue, ^{ block = _didRemoveObjectBlock; }); return block; } - (void)setDidRemoveObjectBlock:(TMDiskCacheObjectBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_didRemoveObjectBlock = [block copy]; }); } - (TMDiskCacheBlock)didRemoveAllObjectsBlock { __block TMDiskCacheBlock block = nil; dispatch_sync(_queue, ^{ block = _didRemoveAllObjectsBlock; }); return block; } - (void)setDidRemoveAllObjectsBlock:(TMDiskCacheBlock)block { __weak TMDiskCache *weakSelf = self; dispatch_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_didRemoveAllObjectsBlock = [block copy]; }); } - (NSUInteger)byteLimit { __block NSUInteger byteLimit = 0; dispatch_sync(_queue, ^{ byteLimit = _byteLimit; }); return byteLimit; } - (void)setByteLimit:(NSUInteger)byteLimit { __weak TMDiskCache *weakSelf = self; dispatch_barrier_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_byteLimit = byteLimit; if (byteLimit > 0) [strongSelf trimDiskToSizeByDate:byteLimit]; }); } - (NSTimeInterval)ageLimit { __block NSTimeInterval ageLimit = 0.0; dispatch_sync(_queue, ^{ ageLimit = _ageLimit; }); return ageLimit; } - (void)setAgeLimit:(NSTimeInterval)ageLimit { __weak TMDiskCache *weakSelf = self; dispatch_barrier_async(_queue, ^{ TMDiskCache *strongSelf = weakSelf; if (!strongSelf) return; strongSelf->_ageLimit = ageLimit; [strongSelf trimToAgeLimitRecursively]; }); } @end