YYDiskCache.m 12 KB

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