YYDiskCache.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. //
  2. // YYDiskCache.m
  3. // YYCache <https://github.com/ibireme/YYCache>
  4. //
  5. // Created by ibireme on 15/2/11.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYDiskCache.h"
  12. #import "YYKVStorage.h"
  13. #import <UIKit/UIKit.h>
  14. #import <CommonCrypto/CommonCrypto.h>
  15. #import <objc/runtime.h>
  16. #import <time.h>
  17. #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
  18. #define Unlock() dispatch_semaphore_signal(self->_lock)
  19. static const int extended_data_key;
  20. /// Free disk space in bytes.
  21. static int64_t _YYDiskSpaceFree() {
  22. NSError *error = nil;
  23. NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
  24. if (error) return -1;
  25. int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];
  26. if (space < 0) space = -1;
  27. return space;
  28. }
  29. /// String's md5 hash.
  30. static NSString *_YYNSStringMD5(NSString *string) {
  31. if (!string) return nil;
  32. NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
  33. unsigned char result[CC_MD5_DIGEST_LENGTH];
  34. CC_MD5(data.bytes, (CC_LONG)data.length, result);
  35. return [NSString stringWithFormat:
  36. @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
  37. result[0], result[1], result[2], result[3],
  38. result[4], result[5], result[6], result[7],
  39. result[8], result[9], result[10], result[11],
  40. result[12], result[13], result[14], result[15]
  41. ];
  42. }
  43. /// weak reference for all instances
  44. static NSMapTable *_globalInstances;
  45. static dispatch_semaphore_t _globalInstancesLock;
  46. static void _YYDiskCacheInitGlobal() {
  47. static dispatch_once_t onceToken;
  48. dispatch_once(&onceToken, ^{
  49. _globalInstancesLock = dispatch_semaphore_create(1);
  50. _globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
  51. });
  52. }
  53. static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
  54. if (path.length == 0) return nil;
  55. _YYDiskCacheInitGlobal();
  56. dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
  57. id cache = [_globalInstances objectForKey:path];
  58. dispatch_semaphore_signal(_globalInstancesLock);
  59. return cache;
  60. }
  61. static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
  62. if (cache.path.length == 0) return;
  63. _YYDiskCacheInitGlobal();
  64. dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
  65. [_globalInstances setObject:cache forKey:cache.path];
  66. dispatch_semaphore_signal(_globalInstancesLock);
  67. }
  68. @implementation YYDiskCache {
  69. YYKVStorage *_kv;
  70. dispatch_semaphore_t _lock;
  71. dispatch_queue_t _queue;
  72. }
  73. - (void)_trimRecursively {
  74. __weak typeof(self) _self = self;
  75. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  76. __strong typeof(_self) self = _self;
  77. if (!self) return;
  78. [self _trimInBackground];
  79. [self _trimRecursively];
  80. });
  81. }
  82. - (void)_trimInBackground {
  83. __weak typeof(self) _self = self;
  84. dispatch_async(_queue, ^{
  85. __strong typeof(_self) self = _self;
  86. if (!self) return;
  87. Lock();
  88. [self _trimToCost:self.costLimit];
  89. [self _trimToCount:self.countLimit];
  90. [self _trimToAge:self.ageLimit];
  91. [self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
  92. Unlock();
  93. });
  94. }
  95. - (void)_trimToCost:(NSUInteger)costLimit {
  96. if (costLimit >= INT_MAX) return;
  97. [_kv removeItemsToFitSize:(int)costLimit];
  98. }
  99. - (void)_trimToCount:(NSUInteger)countLimit {
  100. if (countLimit >= INT_MAX) return;
  101. [_kv removeItemsToFitCount:(int)countLimit];
  102. }
  103. - (void)_trimToAge:(NSTimeInterval)ageLimit {
  104. if (ageLimit <= 0) {
  105. [_kv removeAllItems];
  106. return;
  107. }
  108. long timestamp = time(NULL);
  109. if (timestamp <= ageLimit) return;
  110. long age = timestamp - ageLimit;
  111. if (age >= INT_MAX) return;
  112. [_kv removeItemsEarlierThanTime:(int)age];
  113. }
  114. - (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace {
  115. if (targetFreeDiskSpace == 0) return;
  116. int64_t totalBytes = [_kv getItemsSize];
  117. if (totalBytes <= 0) return;
  118. int64_t diskFreeBytes = _YYDiskSpaceFree();
  119. if (diskFreeBytes < 0) return;
  120. int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes;
  121. if (needTrimBytes <= 0) return;
  122. int64_t costLimit = totalBytes - needTrimBytes;
  123. if (costLimit < 0) costLimit = 0;
  124. [self _trimToCost:(int)costLimit];
  125. }
  126. - (NSString *)_filenameForKey:(NSString *)key {
  127. NSString *filename = nil;
  128. if (_customFileNameBlock) filename = _customFileNameBlock(key);
  129. if (!filename) filename = _YYNSStringMD5(key);
  130. return filename;
  131. }
  132. - (void)_appWillBeTerminated {
  133. Lock();
  134. _kv = nil;
  135. Unlock();
  136. }
  137. #pragma mark - public
  138. - (void)dealloc {
  139. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
  140. }
  141. - (instancetype)init {
  142. @throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil];
  143. return [self initWithPath:@"" inlineThreshold:0];
  144. }
  145. - (instancetype)initWithPath:(NSString *)path {
  146. return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
  147. }
  148. - (instancetype)initWithPath:(NSString *)path
  149. inlineThreshold:(NSUInteger)threshold {
  150. self = [super init];
  151. if (!self) return nil;
  152. YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
  153. if (globalCache) return globalCache;
  154. YYKVStorageType type;
  155. if (threshold == 0) {
  156. type = YYKVStorageTypeFile;
  157. } else if (threshold == NSUIntegerMax) {
  158. type = YYKVStorageTypeSQLite;
  159. } else {
  160. type = YYKVStorageTypeMixed;
  161. }
  162. YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
  163. if (!kv) return nil;
  164. _kv = kv;
  165. _path = path;
  166. _lock = dispatch_semaphore_create(1);
  167. _queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
  168. _inlineThreshold = threshold;
  169. _countLimit = NSUIntegerMax;
  170. _costLimit = NSUIntegerMax;
  171. _ageLimit = DBL_MAX;
  172. _freeDiskSpaceLimit = 0;
  173. _autoTrimInterval = 60;
  174. [self _trimRecursively];
  175. _YYDiskCacheSetGlobal(self);
  176. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
  177. return self;
  178. }
  179. - (BOOL)containsObjectForKey:(NSString *)key {
  180. if (!key) return NO;
  181. Lock();
  182. BOOL contains = [_kv itemExistsForKey:key];
  183. Unlock();
  184. return contains;
  185. }
  186. - (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block {
  187. if (!block) return;
  188. __weak typeof(self) _self = self;
  189. dispatch_async(_queue, ^{
  190. __strong typeof(_self) self = _self;
  191. BOOL contains = [self containsObjectForKey:key];
  192. block(key, contains);
  193. });
  194. }
  195. - (id<NSCoding>)objectForKey:(NSString *)key {
  196. if (!key) return nil;
  197. Lock();
  198. YYKVStorageItem *item = [_kv getItemForKey:key];
  199. Unlock();
  200. if (!item.value) return nil;
  201. id object = nil;
  202. if (_customUnarchiveBlock) {
  203. object = _customUnarchiveBlock(item.value);
  204. } else {
  205. @try {
  206. object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
  207. }
  208. @catch (NSException *exception) {
  209. // nothing to do...
  210. }
  211. }
  212. if (object && item.extendedData) {
  213. [YYDiskCache setExtendedData:item.extendedData toObject:object];
  214. }
  215. return object;
  216. }
  217. - (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> object))block {
  218. if (!block) return;
  219. __weak typeof(self) _self = self;
  220. dispatch_async(_queue, ^{
  221. __strong typeof(_self) self = _self;
  222. id<NSCoding> object = [self objectForKey:key];
  223. block(key, object);
  224. });
  225. }
  226. - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
  227. if (!key) return;
  228. if (!object) {
  229. [self removeObjectForKey:key];
  230. return;
  231. }
  232. NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
  233. NSData *value = nil;
  234. if (_customArchiveBlock) {
  235. value = _customArchiveBlock(object);
  236. } else {
  237. @try {
  238. value = [NSKeyedArchiver archivedDataWithRootObject:object];
  239. }
  240. @catch (NSException *exception) {
  241. // nothing to do...
  242. }
  243. }
  244. if (!value) return;
  245. NSString *filename = nil;
  246. if (_kv.type != YYKVStorageTypeSQLite) {
  247. if (value.length > _inlineThreshold) {
  248. filename = [self _filenameForKey:key];
  249. }
  250. }
  251. Lock();
  252. [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
  253. Unlock();
  254. }
  255. - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block {
  256. __weak typeof(self) _self = self;
  257. dispatch_async(_queue, ^{
  258. __strong typeof(_self) self = _self;
  259. [self setObject:object forKey:key];
  260. if (block) block();
  261. });
  262. }
  263. - (void)removeObjectForKey:(NSString *)key {
  264. if (!key) return;
  265. Lock();
  266. [_kv removeItemForKey:key];
  267. Unlock();
  268. }
  269. - (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block {
  270. __weak typeof(self) _self = self;
  271. dispatch_async(_queue, ^{
  272. __strong typeof(_self) self = _self;
  273. [self removeObjectForKey:key];
  274. if (block) block(key);
  275. });
  276. }
  277. - (void)removeAllObjects {
  278. Lock();
  279. [_kv removeAllItems];
  280. Unlock();
  281. }
  282. - (void)removeAllObjectsWithBlock:(void(^)(void))block {
  283. __weak typeof(self) _self = self;
  284. dispatch_async(_queue, ^{
  285. __strong typeof(_self) self = _self;
  286. [self removeAllObjects];
  287. if (block) block();
  288. });
  289. }
  290. - (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
  291. endBlock:(void(^)(BOOL error))end {
  292. __weak typeof(self) _self = self;
  293. dispatch_async(_queue, ^{
  294. __strong typeof(_self) self = _self;
  295. if (!self) {
  296. if (end) end(YES);
  297. return;
  298. }
  299. Lock();
  300. [_kv removeAllItemsWithProgressBlock:progress endBlock:end];
  301. Unlock();
  302. });
  303. }
  304. - (NSInteger)totalCount {
  305. Lock();
  306. int count = [_kv getItemsCount];
  307. Unlock();
  308. return count;
  309. }
  310. - (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block {
  311. if (!block) return;
  312. __weak typeof(self) _self = self;
  313. dispatch_async(_queue, ^{
  314. __strong typeof(_self) self = _self;
  315. NSInteger totalCount = [self totalCount];
  316. block(totalCount);
  317. });
  318. }
  319. - (NSInteger)totalCost {
  320. Lock();
  321. int count = [_kv getItemsSize];
  322. Unlock();
  323. return count;
  324. }
  325. - (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block {
  326. if (!block) return;
  327. __weak typeof(self) _self = self;
  328. dispatch_async(_queue, ^{
  329. __strong typeof(_self) self = _self;
  330. NSInteger totalCost = [self totalCost];
  331. block(totalCost);
  332. });
  333. }
  334. - (void)trimToCount:(NSUInteger)count {
  335. Lock();
  336. [self _trimToCount:count];
  337. Unlock();
  338. }
  339. - (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block {
  340. __weak typeof(self) _self = self;
  341. dispatch_async(_queue, ^{
  342. __strong typeof(_self) self = _self;
  343. [self trimToCount:count];
  344. if (block) block();
  345. });
  346. }
  347. - (void)trimToCost:(NSUInteger)cost {
  348. Lock();
  349. [self _trimToCost:cost];
  350. Unlock();
  351. }
  352. - (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block {
  353. __weak typeof(self) _self = self;
  354. dispatch_async(_queue, ^{
  355. __strong typeof(_self) self = _self;
  356. [self trimToCost:cost];
  357. if (block) block();
  358. });
  359. }
  360. - (void)trimToAge:(NSTimeInterval)age {
  361. Lock();
  362. [self _trimToAge:age];
  363. Unlock();
  364. }
  365. - (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block {
  366. __weak typeof(self) _self = self;
  367. dispatch_async(_queue, ^{
  368. __strong typeof(_self) self = _self;
  369. [self trimToAge:age];
  370. if (block) block();
  371. });
  372. }
  373. + (NSData *)getExtendedDataFromObject:(id)object {
  374. if (!object) return nil;
  375. return (NSData *)objc_getAssociatedObject(object, &extended_data_key);
  376. }
  377. + (void)setExtendedData:(NSData *)extendedData toObject:(id)object {
  378. if (!object) return;
  379. objc_setAssociatedObject(object, &extended_data_key, extendedData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  380. }
  381. - (NSString *)description {
  382. if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@:%@)", self.class, self, _name, _path];
  383. else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path];
  384. }
  385. - (BOOL)errorLogsEnabled {
  386. Lock();
  387. BOOL enabled = _kv.errorLogsEnabled;
  388. Unlock();
  389. return enabled;
  390. }
  391. - (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled {
  392. Lock();
  393. _kv.errorLogsEnabled = errorLogsEnabled;
  394. Unlock();
  395. }
  396. @end