123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDMemoryCache.h"
- #import "SDImageCacheConfig.h"
- #import "UIImage+MemoryCacheCost.h"
- #import "SDInternalMacros.h"
- static void * SDMemoryCacheContext = &SDMemoryCacheContext;
- @interface SDMemoryCache <KeyType, ObjectType> ()
- @property (nonatomic, strong, nullable) SDImageCacheConfig *config;
- @property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
- @property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
- @end
- @implementation SDMemoryCache
- - (void)dealloc {
- [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) context:SDMemoryCacheContext];
- [_config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) context:SDMemoryCacheContext];
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- _config = [[SDImageCacheConfig alloc] init];
- [self commonInit];
- }
- return self;
- }
- - (instancetype)initWithConfig:(SDImageCacheConfig *)config {
- self = [super init];
- if (self) {
- _config = config;
- [self commonInit];
- }
- return self;
- }
- - (void)commonInit {
- self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
- self.weakCacheLock = dispatch_semaphore_create(1);
-
- SDImageCacheConfig *config = self.config;
- self.totalCostLimit = config.maxMemoryCost;
- self.countLimit = config.maxMemoryCount;
-
- [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
- [config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
-
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(didReceiveMemoryWarning:)
- name:UIApplicationDidReceiveMemoryWarningNotification
- object:nil];
- #endif
- }
- // 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.
- #if SD_UIKIT
- - (void)didReceiveMemoryWarning:(NSNotification *)notification {
- // Only remove cache, but keep weak cache
- [super removeAllObjects];
- }
- // `setObject:forKey:` just call this with 0 cost. Override this is enough
- - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
- [super setObject:obj forKey:key cost:g];
- if (!self.config.shouldUseWeakMemoryCache) {
- return;
- }
- if (key && obj) {
- // Store weak cache
- SD_LOCK(self.weakCacheLock);
- [self.weakCache setObject:obj forKey:key];
- SD_UNLOCK(self.weakCacheLock);
- }
- }
- - (id)objectForKey:(id)key {
- id obj = [super objectForKey:key];
- if (!self.config.shouldUseWeakMemoryCache) {
- return obj;
- }
- if (key && !obj) {
- // Check weak cache
- SD_LOCK(self.weakCacheLock);
- obj = [self.weakCache objectForKey:key];
- SD_UNLOCK(self.weakCacheLock);
- if (obj) {
- // Sync cache
- NSUInteger cost = 0;
- if ([obj isKindOfClass:[UIImage class]]) {
- cost = [(UIImage *)obj sd_memoryCost];
- }
- [super setObject:obj forKey:key cost:cost];
- }
- }
- return obj;
- }
- - (void)removeObjectForKey:(id)key {
- [super removeObjectForKey:key];
- if (!self.config.shouldUseWeakMemoryCache) {
- return;
- }
- if (key) {
- // Remove weak cache
- SD_LOCK(self.weakCacheLock);
- [self.weakCache removeObjectForKey:key];
- SD_UNLOCK(self.weakCacheLock);
- }
- }
- - (void)removeAllObjects {
- [super removeAllObjects];
- if (!self.config.shouldUseWeakMemoryCache) {
- return;
- }
- // Manually remove should also remove weak cache
- SD_LOCK(self.weakCacheLock);
- [self.weakCache removeAllObjects];
- SD_UNLOCK(self.weakCacheLock);
- }
- #endif
- #pragma mark - KVO
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
- if (context == SDMemoryCacheContext) {
- if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCost))]) {
- self.totalCostLimit = self.config.maxMemoryCost;
- } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxMemoryCount))]) {
- self.countLimit = self.config.maxMemoryCount;
- }
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- }
- @end
|