YYMemoryCache.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. //
  2. // YYMemoryCache.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/2/7.
  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 "YYMemoryCache.h"
  12. #import <UIKit/UIKit.h>
  13. #import <CoreFoundation/CoreFoundation.h>
  14. #import <QuartzCore/QuartzCore.h>
  15. #import <pthread.h>
  16. #if __has_include("YYDispatchQueuePool.h")
  17. #import "YYDispatchQueuePool.h"
  18. #endif
  19. #ifdef YYDispatchQueuePool_h
  20. static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
  21. return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
  22. }
  23. #else
  24. static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
  25. return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
  26. }
  27. #endif
  28. /**
  29. A node in linked map.
  30. Typically, you should not use this class directly.
  31. */
  32. @interface _YYLinkedMapNode : NSObject {
  33. @package
  34. __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
  35. __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
  36. id _key;
  37. id _value;
  38. NSUInteger _cost;
  39. NSTimeInterval _time;
  40. }
  41. @end
  42. @implementation _YYLinkedMapNode
  43. @end
  44. /**
  45. A linked map used by YYMemoryCache.
  46. It's not thread-safe and does not validate the parameters.
  47. Typically, you should not use this class directly.
  48. */
  49. @interface _YYLinkedMap : NSObject {
  50. @package
  51. CFMutableDictionaryRef _dic; // do not set object directly
  52. NSUInteger _totalCost;
  53. NSUInteger _totalCount;
  54. _YYLinkedMapNode *_head; // MRU, do not change it directly
  55. _YYLinkedMapNode *_tail; // LRU, do not change it directly
  56. BOOL _releaseOnMainThread;
  57. BOOL _releaseAsynchronously;
  58. }
  59. /// Insert a node at head and update the total cost.
  60. /// Node and node.key should not be nil.
  61. - (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
  62. /// Bring a inner node to header.
  63. /// Node should already inside the dic.
  64. - (void)bringNodeToHead:(_YYLinkedMapNode *)node;
  65. /// Remove a inner node and update the total cost.
  66. /// Node should already inside the dic.
  67. - (void)removeNode:(_YYLinkedMapNode *)node;
  68. /// Remove tail node if exist.
  69. - (_YYLinkedMapNode *)removeTailNode;
  70. /// Remove all node in background queue.
  71. - (void)removeAll;
  72. @end
  73. @implementation _YYLinkedMap
  74. - (instancetype)init {
  75. self = [super init];
  76. _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  77. _releaseOnMainThread = NO;
  78. _releaseAsynchronously = YES;
  79. return self;
  80. }
  81. - (void)dealloc {
  82. CFRelease(_dic);
  83. }
  84. - (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
  85. CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
  86. _totalCost += node->_cost;
  87. _totalCount++;
  88. if (_head) {
  89. node->_next = _head;
  90. _head->_prev = node;
  91. _head = node;
  92. } else {
  93. _head = _tail = node;
  94. }
  95. }
  96. - (void)bringNodeToHead:(_YYLinkedMapNode *)node {
  97. if (_head == node) return;
  98. if (_tail == node) {
  99. _tail = node->_prev;
  100. _tail->_next = nil;
  101. } else {
  102. node->_next->_prev = node->_prev;
  103. node->_prev->_next = node->_next;
  104. }
  105. node->_next = _head;
  106. node->_prev = nil;
  107. _head->_prev = node;
  108. _head = node;
  109. }
  110. - (void)removeNode:(_YYLinkedMapNode *)node {
  111. CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
  112. _totalCost -= node->_cost;
  113. _totalCount--;
  114. if (node->_next) node->_next->_prev = node->_prev;
  115. if (node->_prev) node->_prev->_next = node->_next;
  116. if (_head == node) _head = node->_next;
  117. if (_tail == node) _tail = node->_prev;
  118. }
  119. - (_YYLinkedMapNode *)removeTailNode {
  120. if (!_tail) return nil;
  121. _YYLinkedMapNode *tail = _tail;
  122. CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
  123. _totalCost -= _tail->_cost;
  124. _totalCount--;
  125. if (_head == _tail) {
  126. _head = _tail = nil;
  127. } else {
  128. _tail = _tail->_prev;
  129. _tail->_next = nil;
  130. }
  131. return tail;
  132. }
  133. - (void)removeAll {
  134. _totalCost = 0;
  135. _totalCount = 0;
  136. _head = nil;
  137. _tail = nil;
  138. if (CFDictionaryGetCount(_dic) > 0) {
  139. CFMutableDictionaryRef holder = _dic;
  140. _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  141. if (_releaseAsynchronously) {
  142. dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  143. dispatch_async(queue, ^{
  144. CFRelease(holder); // hold and release in specified queue
  145. });
  146. } else if (_releaseOnMainThread && !pthread_main_np()) {
  147. dispatch_async(dispatch_get_main_queue(), ^{
  148. CFRelease(holder); // hold and release in specified queue
  149. });
  150. } else {
  151. CFRelease(holder);
  152. }
  153. }
  154. }
  155. @end
  156. @implementation YYMemoryCache {
  157. pthread_mutex_t _lock;
  158. _YYLinkedMap *_lru;
  159. dispatch_queue_t _queue;
  160. }
  161. - (void)_trimRecursively {
  162. __weak typeof(self) _self = self;
  163. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
  164. __strong typeof(_self) self = _self;
  165. if (!self) return;
  166. [self _trimInBackground];
  167. [self _trimRecursively];
  168. });
  169. }
  170. - (void)_trimInBackground {
  171. dispatch_async(_queue, ^{
  172. [self _trimToCost:self->_costLimit];
  173. [self _trimToCount:self->_countLimit];
  174. [self _trimToAge:self->_ageLimit];
  175. });
  176. }
  177. - (void)_trimToCost:(NSUInteger)costLimit {
  178. BOOL finish = NO;
  179. pthread_mutex_lock(&_lock);
  180. if (costLimit == 0) {
  181. [_lru removeAll];
  182. finish = YES;
  183. } else if (_lru->_totalCost <= costLimit) {
  184. finish = YES;
  185. }
  186. pthread_mutex_unlock(&_lock);
  187. if (finish) return;
  188. NSMutableArray *holder = [NSMutableArray new];
  189. while (!finish) {
  190. if (pthread_mutex_trylock(&_lock) == 0) {
  191. if (_lru->_totalCost > costLimit) {
  192. _YYLinkedMapNode *node = [_lru removeTailNode];
  193. if (node) [holder addObject:node];
  194. } else {
  195. finish = YES;
  196. }
  197. pthread_mutex_unlock(&_lock);
  198. } else {
  199. usleep(10 * 1000); //10 ms
  200. }
  201. }
  202. if (holder.count) {
  203. dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  204. dispatch_async(queue, ^{
  205. [holder count]; // release in queue
  206. });
  207. }
  208. }
  209. - (void)_trimToCount:(NSUInteger)countLimit {
  210. BOOL finish = NO;
  211. pthread_mutex_lock(&_lock);
  212. if (countLimit == 0) {
  213. [_lru removeAll];
  214. finish = YES;
  215. } else if (_lru->_totalCount <= countLimit) {
  216. finish = YES;
  217. }
  218. pthread_mutex_unlock(&_lock);
  219. if (finish) return;
  220. NSMutableArray *holder = [NSMutableArray new];
  221. while (!finish) {
  222. if (pthread_mutex_trylock(&_lock) == 0) {
  223. if (_lru->_totalCount > countLimit) {
  224. _YYLinkedMapNode *node = [_lru removeTailNode];
  225. if (node) [holder addObject:node];
  226. } else {
  227. finish = YES;
  228. }
  229. pthread_mutex_unlock(&_lock);
  230. } else {
  231. usleep(10 * 1000); //10 ms
  232. }
  233. }
  234. if (holder.count) {
  235. dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  236. dispatch_async(queue, ^{
  237. [holder count]; // release in queue
  238. });
  239. }
  240. }
  241. - (void)_trimToAge:(NSTimeInterval)ageLimit {
  242. BOOL finish = NO;
  243. NSTimeInterval now = CACurrentMediaTime();
  244. pthread_mutex_lock(&_lock);
  245. if (ageLimit <= 0) {
  246. [_lru removeAll];
  247. finish = YES;
  248. } else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
  249. finish = YES;
  250. }
  251. pthread_mutex_unlock(&_lock);
  252. if (finish) return;
  253. NSMutableArray *holder = [NSMutableArray new];
  254. while (!finish) {
  255. if (pthread_mutex_trylock(&_lock) == 0) {
  256. if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
  257. _YYLinkedMapNode *node = [_lru removeTailNode];
  258. if (node) [holder addObject:node];
  259. } else {
  260. finish = YES;
  261. }
  262. pthread_mutex_unlock(&_lock);
  263. } else {
  264. usleep(10 * 1000); //10 ms
  265. }
  266. }
  267. if (holder.count) {
  268. dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  269. dispatch_async(queue, ^{
  270. [holder count]; // release in queue
  271. });
  272. }
  273. }
  274. - (void)_appDidReceiveMemoryWarningNotification {
  275. if (self.didReceiveMemoryWarningBlock) {
  276. self.didReceiveMemoryWarningBlock(self);
  277. }
  278. if (self.shouldRemoveAllObjectsOnMemoryWarning) {
  279. [self removeAllObjects];
  280. }
  281. }
  282. - (void)_appDidEnterBackgroundNotification {
  283. if (self.didEnterBackgroundBlock) {
  284. self.didEnterBackgroundBlock(self);
  285. }
  286. if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
  287. [self removeAllObjects];
  288. }
  289. }
  290. #pragma mark - public
  291. - (instancetype)init {
  292. self = super.init;
  293. pthread_mutex_init(&_lock, NULL);
  294. _lru = [_YYLinkedMap new];
  295. _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
  296. _countLimit = NSUIntegerMax;
  297. _costLimit = NSUIntegerMax;
  298. _ageLimit = DBL_MAX;
  299. _autoTrimInterval = 5.0;
  300. _shouldRemoveAllObjectsOnMemoryWarning = YES;
  301. _shouldRemoveAllObjectsWhenEnteringBackground = YES;
  302. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  303. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
  304. [self _trimRecursively];
  305. return self;
  306. }
  307. - (void)dealloc {
  308. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  309. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
  310. [_lru removeAll];
  311. pthread_mutex_destroy(&_lock);
  312. }
  313. - (NSUInteger)totalCount {
  314. pthread_mutex_lock(&_lock);
  315. NSUInteger count = _lru->_totalCount;
  316. pthread_mutex_unlock(&_lock);
  317. return count;
  318. }
  319. - (NSUInteger)totalCost {
  320. pthread_mutex_lock(&_lock);
  321. NSUInteger totalCost = _lru->_totalCost;
  322. pthread_mutex_unlock(&_lock);
  323. return totalCost;
  324. }
  325. - (BOOL)releaseOnMainThread {
  326. pthread_mutex_lock(&_lock);
  327. BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
  328. pthread_mutex_unlock(&_lock);
  329. return releaseOnMainThread;
  330. }
  331. - (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
  332. pthread_mutex_lock(&_lock);
  333. _lru->_releaseOnMainThread = releaseOnMainThread;
  334. pthread_mutex_unlock(&_lock);
  335. }
  336. - (BOOL)releaseAsynchronously {
  337. pthread_mutex_lock(&_lock);
  338. BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
  339. pthread_mutex_unlock(&_lock);
  340. return releaseAsynchronously;
  341. }
  342. - (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
  343. pthread_mutex_lock(&_lock);
  344. _lru->_releaseAsynchronously = releaseAsynchronously;
  345. pthread_mutex_unlock(&_lock);
  346. }
  347. - (BOOL)containsObjectForKey:(id)key {
  348. if (!key) return NO;
  349. pthread_mutex_lock(&_lock);
  350. BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
  351. pthread_mutex_unlock(&_lock);
  352. return contains;
  353. }
  354. - (id)objectForKey:(id)key {
  355. if (!key) return nil;
  356. pthread_mutex_lock(&_lock);
  357. _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
  358. if (node) {
  359. node->_time = CACurrentMediaTime();
  360. [_lru bringNodeToHead:node];
  361. }
  362. pthread_mutex_unlock(&_lock);
  363. return node ? node->_value : nil;
  364. }
  365. - (void)setObject:(id)object forKey:(id)key {
  366. [self setObject:object forKey:key withCost:0];
  367. }
  368. - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
  369. if (!key) return;
  370. if (!object) {
  371. [self removeObjectForKey:key];
  372. return;
  373. }
  374. pthread_mutex_lock(&_lock);
  375. _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
  376. NSTimeInterval now = CACurrentMediaTime();
  377. if (node) {
  378. _lru->_totalCost -= node->_cost;
  379. _lru->_totalCost += cost;
  380. node->_cost = cost;
  381. node->_time = now;
  382. node->_value = object;
  383. [_lru bringNodeToHead:node];
  384. } else {
  385. node = [_YYLinkedMapNode new];
  386. node->_cost = cost;
  387. node->_time = now;
  388. node->_key = key;
  389. node->_value = object;
  390. [_lru insertNodeAtHead:node];
  391. }
  392. if (_lru->_totalCost > _costLimit) {
  393. dispatch_async(_queue, ^{
  394. [self trimToCost:_costLimit];
  395. });
  396. }
  397. if (_lru->_totalCount > _countLimit) {
  398. _YYLinkedMapNode *node = [_lru removeTailNode];
  399. if (_lru->_releaseAsynchronously) {
  400. dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  401. dispatch_async(queue, ^{
  402. [node class]; //hold and release in queue
  403. });
  404. } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
  405. dispatch_async(dispatch_get_main_queue(), ^{
  406. [node class]; //hold and release in queue
  407. });
  408. }
  409. }
  410. pthread_mutex_unlock(&_lock);
  411. }
  412. - (void)removeObjectForKey:(id)key {
  413. if (!key) return;
  414. pthread_mutex_lock(&_lock);
  415. _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
  416. if (node) {
  417. [_lru removeNode:node];
  418. if (_lru->_releaseAsynchronously) {
  419. dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
  420. dispatch_async(queue, ^{
  421. [node class]; //hold and release in queue
  422. });
  423. } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
  424. dispatch_async(dispatch_get_main_queue(), ^{
  425. [node class]; //hold and release in queue
  426. });
  427. }
  428. }
  429. pthread_mutex_unlock(&_lock);
  430. }
  431. - (void)removeAllObjects {
  432. pthread_mutex_lock(&_lock);
  433. [_lru removeAll];
  434. pthread_mutex_unlock(&_lock);
  435. }
  436. - (void)trimToCount:(NSUInteger)count {
  437. if (count == 0) {
  438. [self removeAllObjects];
  439. return;
  440. }
  441. [self _trimToCount:count];
  442. }
  443. - (void)trimToCost:(NSUInteger)cost {
  444. [self _trimToCost:cost];
  445. }
  446. - (void)trimToAge:(NSTimeInterval)age {
  447. [self _trimToAge:age];
  448. }
  449. - (NSString *)description {
  450. if (_name) return [[NSString alloc] initWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
  451. else return [[NSString alloc] initWithFormat:@"<%@: %p>", self.class, self];
  452. }
  453. @end