iOS开发- 缓存(TMCache是如何缓存的?)
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); }