SDMemoryCache.m 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDMemoryCache.h"
  9. #import "SDImageCacheConfig.h"
  10. #import "UIImage+MemoryCacheCost.h"
  11. #import "SDInternalMacros.h"
  12. static void * SDMemoryCacheContext = &SDMemoryCacheContext;
  13. @interface SDMemoryCache <KeyType, ObjectType> ()
  14. @property (nonatomic, strong, nullable) SDImageCacheConfig *config;
  15. @property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
  16. @property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
  17. @end
  18. @implementation SDMemoryCache
  19. - (void)dealloc {
  20. [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) context:SDMemoryCacheContext];
  21. [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) context:SDMemoryCacheContext];
  22. #if SD_UIKIT
  23. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  24. #endif
  25. }
  26. - (instancetype)init {
  27. self = [super init];
  28. if (self) {
  29. _config = [[SDImageCacheConfig alloc] init];
  30. [self commonInit];
  31. }
  32. return self;
  33. }
  34. - (instancetype)initWithConfig:(SDImageCacheConfig *)config {
  35. self = [super init];
  36. if (self) {
  37. _config = config;
  38. [self commonInit];
  39. }
  40. return self;
  41. }
  42. - (void)commonInit {
  43. self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
  44. self.weakCacheLock = dispatch_semaphore_create(1);
  45. SDImageCacheConfig *config = self.config;
  46. self.totalCostLimit = config.maxMemoryCost;
  47. self.countLimit = config.maxMemoryCount;
  48. [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
  49. [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
  50. #if SD_UIKIT
  51. [[NSNotificationCenter defaultCenter] addObserver:self
  52. selector:@selector(didReceiveMemoryWarning:)
  53. name:UIApplicationDidReceiveMemoryWarningNotification
  54. object:nil];
  55. #endif
  56. }
  57. // Current this seems no use on macOS (macOS use virtual memory and do not clear cache when memory warning). So we only override on iOS/tvOS platform.
  58. #if SD_UIKIT
  59. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  60. // Only remove cache, but keep weak cache
  61. [super removeAllObjects];
  62. }
  63. // `setObject:forKey:` just call this with 0 cost. Override this is enough
  64. - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
  65. [super setObject:obj forKey:key cost:g];
  66. if (!self.config.shouldUseWeakMemoryCache) {
  67. return;
  68. }
  69. if (key && obj) {
  70. // Store weak cache
  71. SD_LOCK(self.weakCacheLock);
  72. [self.weakCache setObject:obj forKey:key];
  73. SD_UNLOCK(self.weakCacheLock);
  74. }
  75. }
  76. - (id)objectForKey:(id)key {
  77. id obj = [super objectForKey:key];
  78. if (!self.config.shouldUseWeakMemoryCache) {
  79. return obj;
  80. }
  81. if (key && !obj) {
  82. // Check weak cache
  83. SD_LOCK(self.weakCacheLock);
  84. obj = [self.weakCache objectForKey:key];
  85. SD_UNLOCK(self.weakCacheLock);
  86. if (obj) {
  87. // Sync cache
  88. NSUInteger cost = 0;
  89. if ([obj isKindOfClass:[UIImage class]]) {
  90. cost = [(UIImage *)obj sd_memoryCost];
  91. }
  92. [super setObject:obj forKey:key cost:cost];
  93. }
  94. }
  95. return obj;
  96. }
  97. - (void)removeObjectForKey:(id)key {
  98. [super removeObjectForKey:key];
  99. if (!self.config.shouldUseWeakMemoryCache) {
  100. return;
  101. }
  102. if (key) {
  103. // Remove weak cache
  104. SD_LOCK(self.weakCacheLock);
  105. [self.weakCache removeObjectForKey:key];
  106. SD_UNLOCK(self.weakCacheLock);
  107. }
  108. }
  109. - (void)removeAllObjects {
  110. [super removeAllObjects];
  111. if (!self.config.shouldUseWeakMemoryCache) {
  112. return;
  113. }
  114. // Manually remove should also remove weak cache
  115. SD_LOCK(self.weakCacheLock);
  116. [self.weakCache removeAllObjects];
  117. SD_UNLOCK(self.weakCacheLock);
  118. }
  119. #endif
  120. #pragma mark - KVO
  121. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  122. if (context == SDMemoryCacheContext) {
  123. if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
  124. self.totalCostLimit = self.config.maxMemoryCost;
  125. } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
  126. self.countLimit = self.config.maxMemoryCount;
  127. }
  128. } else {
  129. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  130. }
  131. }
  132. @end