PINMemoryCache.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. // PINCache is a modified version of TMCache
  2. // Modifications by Garrett Moon
  3. // Copyright (c) 2015 Pinterest. All rights reserved.
  4. #import "PINMemoryCache.h"
  5. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0
  6. #import <UIKit/UIKit.h>
  7. #endif
  8. static NSString * const PINMemoryCachePrefix = @"com.pinterest.PINMemoryCache";
  9. @interface PINMemoryCache ()
  10. #if OS_OBJECT_USE_OBJC
  11. @property (strong, nonatomic) dispatch_queue_t concurrentQueue;
  12. @property (strong, nonatomic) dispatch_semaphore_t lockSemaphore;
  13. #else
  14. @property (assign, nonatomic) dispatch_queue_t concurrentQueue;
  15. @property (assign, nonatomic) dispatch_semaphore_t lockSemaphore;
  16. #endif
  17. @property (strong, nonatomic) NSMutableDictionary *dictionary;
  18. @property (strong, nonatomic) NSMutableDictionary *dates;
  19. @property (strong, nonatomic) NSMutableDictionary *costs;
  20. @end
  21. @implementation PINMemoryCache
  22. @synthesize ageLimit = _ageLimit;
  23. @synthesize costLimit = _costLimit;
  24. @synthesize totalCost = _totalCost;
  25. @synthesize willAddObjectBlock = _willAddObjectBlock;
  26. @synthesize willRemoveObjectBlock = _willRemoveObjectBlock;
  27. @synthesize willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
  28. @synthesize didAddObjectBlock = _didAddObjectBlock;
  29. @synthesize didRemoveObjectBlock = _didRemoveObjectBlock;
  30. @synthesize didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
  31. @synthesize didReceiveMemoryWarningBlock = _didReceiveMemoryWarningBlock;
  32. @synthesize didEnterBackgroundBlock = _didEnterBackgroundBlock;
  33. #pragma mark - Initialization -
  34. - (void)dealloc
  35. {
  36. [[NSNotificationCenter defaultCenter] removeObserver:self];
  37. #if !OS_OBJECT_USE_OBJC
  38. dispatch_release(_concurrentQueue);
  39. dispatch_release(_lockSemaphore);
  40. _concurrentQueue = nil;
  41. #endif
  42. }
  43. - (instancetype)init
  44. {
  45. if (self = [super init]) {
  46. _lockSemaphore = dispatch_semaphore_create(1);
  47. NSString *queueName = [[NSString alloc] initWithFormat:@"%@.%p", PINMemoryCachePrefix, self];
  48. _concurrentQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT);
  49. _dictionary = [[NSMutableDictionary alloc] init];
  50. _dates = [[NSMutableDictionary alloc] init];
  51. _costs = [[NSMutableDictionary alloc] init];
  52. _willAddObjectBlock = nil;
  53. _willRemoveObjectBlock = nil;
  54. _willRemoveAllObjectsBlock = nil;
  55. _didAddObjectBlock = nil;
  56. _didRemoveObjectBlock = nil;
  57. _didRemoveAllObjectsBlock = nil;
  58. _didReceiveMemoryWarningBlock = nil;
  59. _didEnterBackgroundBlock = nil;
  60. _ageLimit = 0.0;
  61. _costLimit = 0;
  62. _totalCost = 0;
  63. _removeAllObjectsOnMemoryWarning = YES;
  64. _removeAllObjectsOnEnteringBackground = YES;
  65. #if TARGET_OS_IPHONE && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 && !TARGET_OS_WATCH
  66. for (NSString *name in @[UIApplicationDidReceiveMemoryWarningNotification, UIApplicationDidEnterBackgroundNotification]) {
  67. [[NSNotificationCenter defaultCenter] addObserver:self
  68. selector:@selector(didObserveApocalypticNotification:)
  69. name:name
  70. #if !defined(PIN_APP_EXTENSIONS)
  71. object:[UIApplication sharedApplication]];
  72. #else
  73. object:nil];
  74. #endif
  75. }
  76. #endif
  77. }
  78. return self;
  79. }
  80. + (instancetype)sharedCache
  81. {
  82. static id cache;
  83. static dispatch_once_t predicate;
  84. dispatch_once(&predicate, ^{
  85. cache = [[self alloc] init];
  86. });
  87. return cache;
  88. }
  89. #pragma mark - Private Methods -
  90. - (void)didObserveApocalypticNotification:(NSNotification *)notification
  91. {
  92. #if TARGET_OS_IPHONE && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_4_0 && !TARGET_OS_WATCH
  93. if ([[notification name] isEqualToString:UIApplicationDidReceiveMemoryWarningNotification]) {
  94. if (self.removeAllObjectsOnMemoryWarning)
  95. [self removeAllObjects:nil];
  96. __weak PINMemoryCache *weakSelf = self;
  97. dispatch_async(_concurrentQueue, ^{
  98. PINMemoryCache *strongSelf = weakSelf;
  99. if (!strongSelf) {
  100. return;
  101. }
  102. [strongSelf lock];
  103. PINMemoryCacheBlock didReceiveMemoryWarningBlock = strongSelf->_didReceiveMemoryWarningBlock;
  104. [strongSelf unlock];
  105. if (didReceiveMemoryWarningBlock)
  106. didReceiveMemoryWarningBlock(strongSelf);
  107. });
  108. } else if ([[notification name] isEqualToString:UIApplicationDidEnterBackgroundNotification]) {
  109. if (self.removeAllObjectsOnEnteringBackground)
  110. [self removeAllObjects:nil];
  111. __weak PINMemoryCache *weakSelf = self;
  112. dispatch_async(_concurrentQueue, ^{
  113. PINMemoryCache *strongSelf = weakSelf;
  114. if (!strongSelf) {
  115. return;
  116. }
  117. [strongSelf lock];
  118. PINMemoryCacheBlock didEnterBackgroundBlock = strongSelf->_didEnterBackgroundBlock;
  119. [strongSelf unlock];
  120. if (didEnterBackgroundBlock)
  121. didEnterBackgroundBlock(strongSelf);
  122. });
  123. }
  124. #endif
  125. }
  126. - (void)removeObjectAndExecuteBlocksForKey:(NSString *)key
  127. {
  128. [self lock];
  129. id object = _dictionary[key];
  130. NSNumber *cost = _costs[key];
  131. PINMemoryCacheObjectBlock willRemoveObjectBlock = _willRemoveObjectBlock;
  132. PINMemoryCacheObjectBlock didRemoveObjectBlock = _didRemoveObjectBlock;
  133. [self unlock];
  134. if (willRemoveObjectBlock)
  135. willRemoveObjectBlock(self, key, object);
  136. [self lock];
  137. if (cost)
  138. _totalCost -= [cost unsignedIntegerValue];
  139. [_dictionary removeObjectForKey:key];
  140. [_dates removeObjectForKey:key];
  141. [_costs removeObjectForKey:key];
  142. [self unlock];
  143. if (didRemoveObjectBlock)
  144. didRemoveObjectBlock(self, key, nil);
  145. }
  146. - (void)trimMemoryToDate:(NSDate *)trimDate
  147. {
  148. [self lock];
  149. NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
  150. NSDictionary *dates = [_dates copy];
  151. [self unlock];
  152. for (NSString *key in keysSortedByDate) { // oldest objects first
  153. NSDate *accessDate = dates[key];
  154. if (!accessDate)
  155. continue;
  156. if ([accessDate compare:trimDate] == NSOrderedAscending) { // older than trim date
  157. [self removeObjectAndExecuteBlocksForKey:key];
  158. } else {
  159. break;
  160. }
  161. }
  162. }
  163. - (void)trimToCostLimit:(NSUInteger)limit
  164. {
  165. NSUInteger totalCost = 0;
  166. [self lock];
  167. totalCost = _totalCost;
  168. NSArray *keysSortedByCost = [_costs keysSortedByValueUsingSelector:@selector(compare:)];
  169. [self unlock];
  170. if (totalCost <= limit) {
  171. return;
  172. }
  173. for (NSString *key in [keysSortedByCost reverseObjectEnumerator]) { // costliest objects first
  174. [self removeObjectAndExecuteBlocksForKey:key];
  175. [self lock];
  176. totalCost = _totalCost;
  177. [self unlock];
  178. if (totalCost <= limit)
  179. break;
  180. }
  181. }
  182. - (void)trimToCostLimitByDate:(NSUInteger)limit
  183. {
  184. NSUInteger totalCost = 0;
  185. [self lock];
  186. totalCost = _totalCost;
  187. NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
  188. [self unlock];
  189. if (totalCost <= limit)
  190. return;
  191. for (NSString *key in keysSortedByDate) { // oldest objects first
  192. [self removeObjectAndExecuteBlocksForKey:key];
  193. [self lock];
  194. totalCost = _totalCost;
  195. [self unlock];
  196. if (totalCost <= limit)
  197. break;
  198. }
  199. }
  200. - (void)trimToAgeLimitRecursively
  201. {
  202. [self lock];
  203. NSTimeInterval ageLimit = _ageLimit;
  204. [self unlock];
  205. if (ageLimit == 0.0)
  206. return;
  207. NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:-ageLimit];
  208. [self trimMemoryToDate:date];
  209. __weak PINMemoryCache *weakSelf = self;
  210. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ageLimit * NSEC_PER_SEC));
  211. dispatch_after(time, _concurrentQueue, ^(void){
  212. PINMemoryCache *strongSelf = weakSelf;
  213. [strongSelf trimToAgeLimitRecursively];
  214. });
  215. }
  216. #pragma mark - Public Asynchronous Methods -
  217. - (void)objectForKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
  218. {
  219. __weak PINMemoryCache *weakSelf = self;
  220. dispatch_async(_concurrentQueue, ^{
  221. PINMemoryCache *strongSelf = weakSelf;
  222. id object = [strongSelf objectForKey:key];
  223. if (block)
  224. block(strongSelf, key, object);
  225. });
  226. }
  227. - (void)setObject:(id)object forKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
  228. {
  229. [self setObject:object forKey:key withCost:0 block:block];
  230. }
  231. - (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost block:(PINMemoryCacheObjectBlock)block
  232. {
  233. __weak PINMemoryCache *weakSelf = self;
  234. dispatch_async(_concurrentQueue, ^{
  235. PINMemoryCache *strongSelf = weakSelf;
  236. [strongSelf setObject:object forKey:key withCost:cost];
  237. if (block)
  238. block(strongSelf, key, object);
  239. });
  240. }
  241. - (void)removeObjectForKey:(NSString *)key block:(PINMemoryCacheObjectBlock)block
  242. {
  243. __weak PINMemoryCache *weakSelf = self;
  244. dispatch_async(_concurrentQueue, ^{
  245. PINMemoryCache *strongSelf = weakSelf;
  246. [strongSelf removeObjectForKey:key];
  247. if (block)
  248. block(strongSelf, key, nil);
  249. });
  250. }
  251. - (void)trimToDate:(NSDate *)trimDate block:(PINMemoryCacheBlock)block
  252. {
  253. __weak PINMemoryCache *weakSelf = self;
  254. dispatch_async(_concurrentQueue, ^{
  255. PINMemoryCache *strongSelf = weakSelf;
  256. [strongSelf trimToDate:trimDate];
  257. if (block)
  258. block(strongSelf);
  259. });
  260. }
  261. - (void)trimToCost:(NSUInteger)cost block:(PINMemoryCacheBlock)block
  262. {
  263. __weak PINMemoryCache *weakSelf = self;
  264. dispatch_async(_concurrentQueue, ^{
  265. PINMemoryCache *strongSelf = weakSelf;
  266. [strongSelf trimToCost:cost];
  267. if (block)
  268. block(strongSelf);
  269. });
  270. }
  271. - (void)trimToCostByDate:(NSUInteger)cost block:(PINMemoryCacheBlock)block
  272. {
  273. __weak PINMemoryCache *weakSelf = self;
  274. dispatch_async(_concurrentQueue, ^{
  275. PINMemoryCache *strongSelf = weakSelf;
  276. [strongSelf trimToCostByDate:cost];
  277. if (block)
  278. block(strongSelf);
  279. });
  280. }
  281. - (void)removeAllObjects:(PINMemoryCacheBlock)block
  282. {
  283. __weak PINMemoryCache *weakSelf = self;
  284. dispatch_async(_concurrentQueue, ^{
  285. PINMemoryCache *strongSelf = weakSelf;
  286. [strongSelf removeAllObjects];
  287. if (block)
  288. block(strongSelf);
  289. });
  290. }
  291. - (void)enumerateObjectsWithBlock:(PINMemoryCacheObjectBlock)block completionBlock:(PINMemoryCacheBlock)completionBlock
  292. {
  293. __weak PINMemoryCache *weakSelf = self;
  294. dispatch_async(_concurrentQueue, ^{
  295. PINMemoryCache *strongSelf = weakSelf;
  296. [strongSelf enumerateObjectsWithBlock:block];
  297. if (completionBlock)
  298. completionBlock(strongSelf);
  299. });
  300. }
  301. #pragma mark - Public Synchronous Methods -
  302. - (__nullable id)objectForKey:(NSString *)key
  303. {
  304. if (!key)
  305. return nil;
  306. [self lock];
  307. id object = _dictionary[key];
  308. [self unlock];
  309. if (object) {
  310. [self lock];
  311. _dates[key] = [[NSDate alloc] init];
  312. [self unlock];
  313. }
  314. return object;
  315. }
  316. - (void)setObject:(id)object forKey:(NSString *)key
  317. {
  318. [self setObject:object forKey:key withCost:0];
  319. }
  320. - (void)setObject:(id)object forKey:(NSString *)key withCost:(NSUInteger)cost
  321. {
  322. if (!key || !object)
  323. return;
  324. [self lock];
  325. PINMemoryCacheObjectBlock willAddObjectBlock = _willAddObjectBlock;
  326. PINMemoryCacheObjectBlock didAddObjectBlock = _didAddObjectBlock;
  327. NSUInteger costLimit = _costLimit;
  328. [self unlock];
  329. if (willAddObjectBlock)
  330. willAddObjectBlock(self, key, object);
  331. [self lock];
  332. _dictionary[key] = object;
  333. _dates[key] = [[NSDate alloc] init];
  334. _costs[key] = @(cost);
  335. _totalCost += cost;
  336. [self unlock];
  337. if (didAddObjectBlock)
  338. didAddObjectBlock(self, key, object);
  339. if (costLimit > 0)
  340. [self trimToCostByDate:costLimit];
  341. }
  342. - (void)removeObjectForKey:(NSString *)key
  343. {
  344. if (!key)
  345. return;
  346. [self removeObjectAndExecuteBlocksForKey:key];
  347. }
  348. - (void)trimToDate:(NSDate *)trimDate
  349. {
  350. if (!trimDate)
  351. return;
  352. if ([trimDate isEqualToDate:[NSDate distantPast]]) {
  353. [self removeAllObjects];
  354. return;
  355. }
  356. [self trimMemoryToDate:trimDate];
  357. }
  358. - (void)trimToCost:(NSUInteger)cost
  359. {
  360. [self trimToCostLimit:cost];
  361. }
  362. - (void)trimToCostByDate:(NSUInteger)cost
  363. {
  364. [self trimToCostLimitByDate:cost];
  365. }
  366. - (void)removeAllObjects
  367. {
  368. [self lock];
  369. PINMemoryCacheBlock willRemoveAllObjectsBlock = _willRemoveAllObjectsBlock;
  370. PINMemoryCacheBlock didRemoveAllObjectsBlock = _didRemoveAllObjectsBlock;
  371. [self unlock];
  372. if (willRemoveAllObjectsBlock)
  373. willRemoveAllObjectsBlock(self);
  374. [self lock];
  375. [_dictionary removeAllObjects];
  376. [_dates removeAllObjects];
  377. [_costs removeAllObjects];
  378. _totalCost = 0;
  379. [self unlock];
  380. if (didRemoveAllObjectsBlock)
  381. didRemoveAllObjectsBlock(self);
  382. }
  383. - (void)enumerateObjectsWithBlock:(PINMemoryCacheObjectBlock)block
  384. {
  385. if (!block)
  386. return;
  387. [self lock];
  388. NSArray *keysSortedByDate = [_dates keysSortedByValueUsingSelector:@selector(compare:)];
  389. for (NSString *key in keysSortedByDate) {
  390. block(self, key, _dictionary[key]);
  391. }
  392. [self unlock];
  393. }
  394. #pragma mark - Public Thread Safe Accessors -
  395. - (PINMemoryCacheObjectBlock)willAddObjectBlock
  396. {
  397. [self lock];
  398. PINMemoryCacheObjectBlock block = _willAddObjectBlock;
  399. [self unlock];
  400. return block;
  401. }
  402. - (void)setWillAddObjectBlock:(PINMemoryCacheObjectBlock)block
  403. {
  404. [self lock];
  405. _willAddObjectBlock = [block copy];
  406. [self unlock];
  407. }
  408. - (PINMemoryCacheObjectBlock)willRemoveObjectBlock
  409. {
  410. [self lock];
  411. PINMemoryCacheObjectBlock block = _willRemoveObjectBlock;
  412. [self unlock];
  413. return block;
  414. }
  415. - (void)setWillRemoveObjectBlock:(PINMemoryCacheObjectBlock)block
  416. {
  417. [self lock];
  418. _willRemoveObjectBlock = [block copy];
  419. [self unlock];
  420. }
  421. - (PINMemoryCacheBlock)willRemoveAllObjectsBlock
  422. {
  423. [self lock];
  424. PINMemoryCacheBlock block = _willRemoveAllObjectsBlock;
  425. [self unlock];
  426. return block;
  427. }
  428. - (void)setWillRemoveAllObjectsBlock:(PINMemoryCacheBlock)block
  429. {
  430. [self lock];
  431. _willRemoveAllObjectsBlock = [block copy];
  432. [self unlock];
  433. }
  434. - (PINMemoryCacheObjectBlock)didAddObjectBlock
  435. {
  436. [self lock];
  437. PINMemoryCacheObjectBlock block = _didAddObjectBlock;
  438. [self unlock];
  439. return block;
  440. }
  441. - (void)setDidAddObjectBlock:(PINMemoryCacheObjectBlock)block
  442. {
  443. [self lock];
  444. _didAddObjectBlock = [block copy];
  445. [self unlock];
  446. }
  447. - (PINMemoryCacheObjectBlock)didRemoveObjectBlock
  448. {
  449. [self lock];
  450. PINMemoryCacheObjectBlock block = _didRemoveObjectBlock;
  451. [self unlock];
  452. return block;
  453. }
  454. - (void)setDidRemoveObjectBlock:(PINMemoryCacheObjectBlock)block
  455. {
  456. [self lock];
  457. _didRemoveObjectBlock = [block copy];
  458. [self unlock];
  459. }
  460. - (PINMemoryCacheBlock)didRemoveAllObjectsBlock
  461. {
  462. [self lock];
  463. PINMemoryCacheBlock block = _didRemoveAllObjectsBlock;
  464. [self unlock];
  465. return block;
  466. }
  467. - (void)setDidRemoveAllObjectsBlock:(PINMemoryCacheBlock)block
  468. {
  469. [self lock];
  470. _didRemoveAllObjectsBlock = [block copy];
  471. [self unlock];
  472. }
  473. - (PINMemoryCacheBlock)didReceiveMemoryWarningBlock
  474. {
  475. [self lock];
  476. PINMemoryCacheBlock block = _didReceiveMemoryWarningBlock;
  477. [self unlock];
  478. return block;
  479. }
  480. - (void)setDidReceiveMemoryWarningBlock:(PINMemoryCacheBlock)block
  481. {
  482. [self lock];
  483. _didReceiveMemoryWarningBlock = [block copy];
  484. [self unlock];
  485. }
  486. - (PINMemoryCacheBlock)didEnterBackgroundBlock
  487. {
  488. [self lock];
  489. PINMemoryCacheBlock block = _didEnterBackgroundBlock;
  490. [self unlock];
  491. return block;
  492. }
  493. - (void)setDidEnterBackgroundBlock:(PINMemoryCacheBlock)block
  494. {
  495. [self lock];
  496. _didEnterBackgroundBlock = [block copy];
  497. [self unlock];
  498. }
  499. - (NSTimeInterval)ageLimit
  500. {
  501. [self lock];
  502. NSTimeInterval ageLimit = _ageLimit;
  503. [self unlock];
  504. return ageLimit;
  505. }
  506. - (void)setAgeLimit:(NSTimeInterval)ageLimit
  507. {
  508. [self lock];
  509. _ageLimit = ageLimit;
  510. [self unlock];
  511. [self trimToAgeLimitRecursively];
  512. }
  513. - (NSUInteger)costLimit
  514. {
  515. [self lock];
  516. NSUInteger costLimit = _costLimit;
  517. [self unlock];
  518. return costLimit;
  519. }
  520. - (void)setCostLimit:(NSUInteger)costLimit
  521. {
  522. [self lock];
  523. _costLimit = costLimit;
  524. [self unlock];
  525. if (costLimit > 0)
  526. [self trimToCostLimitByDate:costLimit];
  527. }
  528. - (NSUInteger)totalCost
  529. {
  530. [self lock];
  531. NSUInteger cost = _totalCost;
  532. [self unlock];
  533. return cost;
  534. }
  535. - (void)lock
  536. {
  537. dispatch_semaphore_wait(_lockSemaphore, DISPATCH_TIME_FOREVER);
  538. }
  539. - (void)unlock
  540. {
  541. dispatch_semaphore_signal(_lockSemaphore);
  542. }
  543. @end