TMCache 是Tumblr使用的緩存系統(github:https://github.com/tumblr/TMCache),它由兩部分組成:磁盤緩存和內存緩存。(目前已經停止維護)
特點:
1. 由GCD支持
2. 線程安全
3. 如果收到內存警告或者APP進入后台, 內存緩存將被清理。磁盤緩存需要手動清理,或者設置時間/大小限制
4. 能夠緩存任何支持NSCoding的對象(最重要的就是UIImage),通過key存取
TMCache由三個類構成,TMCache,TMDiskCache和TMMemoryCache。TMCache對TMDiskCache和TMMemoryCache進行了封裝,其本身並未做實質的緩存工作。
TMCache:
TMCache擁有一個concurrent queue,基本上所有存取對象的操作都在這個queue中進行,可以進行異步或者同步存取(異步方法是主體,同步方法是通過信號量將異步變為同步的)。
異步取對象方法
- TMCache先在TMMemoryCache中取,如果沒有再在TMDiskCache中取
- 另外每次將block添加到queue之前,都要捕獲self ,並且在block中判斷self是否還存在
- 在TMDiskCache中取到對象之后,立即將對象存到TMMemoryCache,這樣下次就直接可以從TMMemoryCache中獲取了
- (void)objectForKey:(NSString *)key block:(TMCacheObjectBlock)block { if (!key || !block) return; __weak TMCache *weakSelf = self; dispatch_async(_queue, ^{ TMCache *strongSelf = weakSelf; if (!strongSelf) return; __weak TMCache *weakSelf = strongSelf; [strongSelf->_memoryCache objectForKey:key block:^(TMMemoryCache *cache, NSString *key, id object) { TMCache *strongSelf = weakSelf; if (!strongSelf) return; if (object) { [strongSelf->_diskCache fileURLForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) { // update the access time on disk }]; __weak TMCache *weakSelf = strongSelf; dispatch_async(strongSelf->_queue, ^{ TMCache *strongSelf = weakSelf; if (strongSelf) block(strongSelf, key, object); }); } else { __weak TMCache *weakSelf = strongSelf; [strongSelf->_diskCache objectForKey:key block:^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) { TMCache *strongSelf = weakSelf; if (!strongSelf) return; [strongSelf->_memoryCache setObject:object forKey:key block:nil]; __weak TMCache *weakSelf = strongSelf; dispatch_async(strongSelf->_queue, ^{ TMCache *strongSelf = weakSelf; if (strongSelf) block(strongSelf, key, object); }); }]; } }]; }); }
異步存對象的方法:
- 同時存到磁盤和內存中,使用dispatch_group_create();dispatch_group_enter(group);dispatch_group_leave(group)和
dispatch_group_notify(group,queue,block)保持兩個操作的同步(兩個操作都完成后,才算是成功)
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(TMCacheObjectBlock)block { if (!key || !object) return; dispatch_group_t group = nil; TMMemoryCacheObjectBlock memBlock = nil; TMDiskCacheObjectBlock diskBlock = nil; if (block) { group = dispatch_group_create(); dispatch_group_enter(group); dispatch_group_enter(group); memBlock = ^(TMMemoryCache *cache, NSString *key, id object) { dispatch_group_leave(group); }; diskBlock = ^(TMDiskCache *cache, NSString *key, id <NSCoding> object, NSURL *fileURL) { dispatch_group_leave(group); }; } [_memoryCache setObject:object forKey:key block:memBlock]; [_diskCache setObject:object forKey:key block:diskBlock]; if (group) { __weak TMCache *weakSelf = self; dispatch_group_notify(group, _queue, ^{ TMCache *strongSelf = weakSelf; if (strongSelf) block(strongSelf, key, object); }); #if !OS_OBJECT_USE_OBJC dispatch_release(group); #endif } }
同步存對象:
- 使用dispatch_semaphore_t將上述異步方法變為同步。
- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key { if (!object || !key) return; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self setObject:object forKey:key block:^(TMCache *cache, NSString *key, id object) { dispatch_semaphore_signal(semaphore); }]; dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); #if !OS_OBJECT_USE_OBJC dispatch_release(semaphore); #endif }
TMDiskCache
- 擁有一個serialed queue,基本所有存取操作都在這個queue中進行,由於是串行queue,所以保證了線程安全。
-初始化時,掃描緩存所在的文件夾,用dictionary記錄每個緩存對象文件的修改日期(最后一次訪問的日期),大小,並在以后的存、取緩存時更新(將來用作刪除緩存的依據)
- 以磁盤文件的url(處理后)為key,使用NSKeyedUnarchiver進行存取(每次取,都要更新最后一次訪問日期)
- 擁有will/didAddObjectBlock,will/didRemoveObjectBlock,will/didRemoveAllObjectsBlock在進行相關操作前后分別調用
TMMemoryCache:
- 擁有一個並行(concurrent) queue,存對象,和刪除對象的時候,使用dispatch_barrier_async,保證線程安全
(dispatch_barrier_async,作用是在開始執行barrier block時,檢查並行隊列中是否有其他正在執行的block,如果有就先將它們執行完而且不執行其他尚未執行的block,然后再執行barrier block,barrier block執行完畢后,再執行其他block)
- 擁有didReceiveMemoryWarningBlock,didEnterBackgroundBlock,對內存警告和進入后台進行處理
- 使用dictionary存儲key和緩存對象
PINCache 是TMCache 的一個fork,解決了重度使用TMCache造成的死鎖問題(為什么死鎖?還沒有搞明白)。它保證線程安全的方式是使用semaphore.(DiskCache中的queue也是並行的)
- 初始化時創建一個 dispatch_semaphore_t lockSemaphore = dispatch_semaphore_create(1) (注意創建時的value 是 1)
- 之后對於所有可能不安全的操作均放在dispatch_semaphore_wait(_lockSemaphore, DISPATCH_TIME_FOREVER) 和dispatch_semaphore_signal(_lockSemaphore)之間進行,即以下的lock 和unlock 方法之間
- (void)lock { dispatch_semaphore_wait(_lockSemaphore, DISPATCH_TIME_FOREVER); } - (void)unlock { dispatch_semaphore_signal(_lockSemaphore); }