美文网首页
SDWebImage 源码分析之 SDMemoryCache 和

SDWebImage 源码分析之 SDMemoryCache 和

作者: 孙掌门 | 来源:发表于2020-07-09 16:19 被阅读0次

SDMemoryCache 源码分析

上次我们分析过 AFN 的源码,也分析过 AFN 的 内存缓存,这里再分析下 SD 的缓存,AFN 是 自己设计的缓存,然后超过了预期的大小之后,开始循环清除,直到剩下预期可接受的大小为止,这里 SD 的缓存策略怎么设计的呢?

/**
 A memory cache which auto purge the cache on memory warning and support weak cache.
 */
@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType> <SDMemoryCache>

@property (nonatomic, strong, nonnull, readonly) SDImageCacheConfig *config;

@end

这里是集成系统 NSCache 的,并且是个泛型类,实现 SDMemoryCache 这个协议,这个协议也就告诉我们,我们可以自定义实现内存缓存,实现协议的这些方法就可以。

}

- (void)commonInit {
    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
    self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
    self.weakCacheLock = dispatch_semaphore_create(1);

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveMemoryWarning:)
                                                 name:UIApplicationDidReceiveMemoryWarningNotification
                                               object:nil];
#endif
}

SDImageCacheConfig 是一个配置,可以设置缓存的数量限制和成本内存限制,当前类是继承自NSCache这个类的,所以直接设置给父类就可以,并且当前缓存类还建立了一个弱缓存,是基于 NSMapTable 数据结构的,key 是 strong ,value 是 weak 的,然后监听系统的内存警告,NSCache 是不能自动监听内存警告的,当收到内存警告的时候,会清楚系统 nscache 存储的所有数据

- (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);
}

添加删除,都是通过信号量来控制线程安全的,可以看到没有给父类 nscache 加锁,因为系统的nscache是 线程安全的,我们可以看到设置值会先给父类设置,如果配置没有设置弱引用缓存直接返回,如果设置了,就往弱引用缓存里面添加一份,获取值的时候先从父类里面取,如果配置没有设置从弱引用取的话,直接返回,如果设置了,接着判断,nscache是否能够获取到,因为,nscache 会根据系统自己的策略,自己清楚缓存,这时候就从我们的弱引用缓存中获取,获取到了之后,再往nscache里面放一份,只有从nscache没有获取到才会执行此操作,可以看出,set方法是nscache和自己的弱引用缓存都存储,get的时候,如果nscache没有,才从弱引用缓存中去,然后存储到nscache中,

- (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];
    }
}

对于 kvo ,监听totalCostLimit和maxMemoryCost的设置,然后动态设置给nscache。

这就是 SD 的内存缓存,要比 AFN 还要简单,但我会提出如下几点疑问,欢迎评论区发表意见

  1. 为什么要用 NSCache 和 自己再创建一个弱引用缓存,要存储两份?就用弱引用缓存不行吗?
  2. 信号量这里面是单读单写的,没有多读单写性能好,这里是有什么考虑吗?

SDDiskCache 源码分析

最开始我理解的内存缓存和磁盘缓存,我以为磁盘缓存会像 YYCache 一样,存储到数据库,过段时间写两片 YYCache 的文章,结果看到代码后,我发现我错了,我们看他的初始化,你就明白了


- (void)commonInit {
    if (self.config.fileManager) {
        self.fileManager = self.config.fileManager;
    } else {
        self.fileManager = [NSFileManager new];
    }
}

如果看到这里你还不明白怎么存储,那就往后看吧


- (BOOL)containsDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    BOOL exists = [self.fileManager fileExistsAtPath:filePath];
    
    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    if (!exists) {
        exists = [self.fileManager fileExistsAtPath:filePath.stringByDeletingPathExtension];
    }
    
    return exists;
}

- (NSData *)dataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    // fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
    // checking the key with and without the extension
    data = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    
    return nil;
}

- (void)setData:(NSData *)data forKey:(NSString *)key {
    NSParameterAssert(data);
    NSParameterAssert(key);
    if (![self.fileManager fileExistsAtPath:self.diskCachePath]) {
        [self.fileManager createDirectoryAtPath:self.diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    // transform to NSURL
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    
    [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    
    // disable iCloud backup
    if (self.config.shouldDisableiCloud) {
        // ignore iCloud backup resource value error
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

- (NSData *)extendedDataForKey:(NSString *)key {
    NSParameterAssert(key);
    
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    
    NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
    
    return extendedData;
}

- (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key {
    NSParameterAssert(key);
    // get cache Path for image key
    NSString *cachePathForKey = [self cachePathForKey:key];
    
    if (!extendedData) {
        // Remove
        [SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
    } else {
        // Override
        [SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
    }
}

- (void)removeDataForKey:(NSString *)key {
    NSParameterAssert(key);
    NSString *filePath = [self cachePathForKey:key];
    [self.fileManager removeItemAtPath:filePath error:nil];
}

- (void)removeAllData {
    [self.fileManager removeItemAtPath:self.diskCachePath error:nil];
    [self.fileManager createDirectoryAtPath:self.diskCachePath
            withIntermediateDirectories:YES
                             attributes:nil
                                  error:NULL];
}

这里是对数据的增删查的处理,get 方法是通过 dataWithContentsOfFile api,set 是通过 [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; 这个 api,然后根据配置判断是否进行 iCloud 存储

    // ignore iCloud backup resource value error
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }

删除是通过 [self.fileManager removeItemAtPath:filePath error:nil]; api ,都是通过系统 api,接下来我们来看看是怎么处理过期数据的

// 移除过期数据
- (void)removeExpiredData {
    NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
    
    // Compute content date key to be used for tests
    // 过期时间的依据,访问时间,内容修改时间,创建时间或者属性修改时间
    NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
    switch (self.config.diskCacheExpireType) {
        case SDImageCacheConfigExpireTypeAccessDate:
            cacheContentDateKey = NSURLContentAccessDateKey;
            break;
        case SDImageCacheConfigExpireTypeModificationDate:
            cacheContentDateKey = NSURLContentModificationDateKey;
            break;
        case SDImageCacheConfigExpireTypeCreationDate:
            cacheContentDateKey = NSURLCreationDateKey;
            break;
        case SDImageCacheConfigExpireTypeChangeDate:
            cacheContentDateKey = NSURLAttributeModificationDateKey;
            break;
        default:
            break;
    }
    // 想要了解的属性
    NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
    
    // This enumerator prefetches useful properties for our cache files.
    // 遍历文件,跳过隐藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];
    // 过期时间
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
    NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
    NSUInteger currentCacheSize = 0;
    
    // Enumerate all of the files in the cache directory.  This loop has two purposes:
    //
    //  1. Removing files that are older than the expiration date.
    //  2. Storing file attributes for the size-based cleanup pass.
    NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
    for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        // 根据 url 获取返回结果,从中获取想要或崎岖的属性
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        // Skip directories and errors.
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        // Remove files that are older than the expiration date;
        // 获取修改时间
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 过期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        // Store a reference to this file and account for its total size.
        // 将文件总大小存储下来
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }
    // 将过期文件删除
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }
    
    // If our remaining disk cache exceeds a configured maximum size, perform a second
    // size-based cleanup pass.  We delete the oldest files first.
    NSUInteger maxDiskSize = self.config.maxDiskSize;
    
    // 如果当前磁盘存储文件的总大小大于配置的大小
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
        // Sort the remaining cache files by their last modification time or last access time (oldest first).
        // 将剩余的文件按照访问时间进行排序,最老的放在最前面
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        // Delete files until we fall below our desired cache size.
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                // 挨个删除,知道大小达到我们预期的值,也就是设置的最大值的一半
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }
}

先是根据配置,设置选择文件的访问时间,是以何种策略来淘汰文件,是根据创建时间超时,还是内容修改时间超时等依据,然后创建一个 数据,想要查看文件的哪些属性 NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];,这里是查看文件是否为目录,访问时间属性,和文件的总大小属性,然后拿出所有文件的 url

 // 遍历文件,跳过隐藏文件
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                               includingPropertiesForKeys:resourceKeys
                                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                             errorHandler:NULL];

拿到总的结果之后,然后根据配置算出真正的过期时间

  // 过期时间
    NSDate *expirationDate = (self.config.maxDiskAge < 0) ? nil: [NSDate dateWithTimeIntervalSinceNow:-self.config.maxDiskAge];
 for (NSURL *fileURL in fileEnumerator) {
        NSError *error;
        // 根据 url 获取返回结果,从中获取想要或崎岖的属性
        NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
        
        // Skip directories and errors.
        if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
            continue;
        }
        
        // Remove files that are older than the expiration date;
        // 获取修改时间
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 过期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }
        
        // Store a reference to this file and account for its total size.
        // 将文件总大小存储下来
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;
    }

然后根据遍历的 url 结果,遍历每一个文件,在从每一个文件中取出我们想要的三个属性,// 根据 url 获取返回结果,从中获取想要或崎岖的属性 NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error]; 这里是取出我们关心的属性,然后接下来,判断下,如果这个文件是目录额话,那么就continue跳过,然后再获取修改时间属性,

 // 获取修改时间
        NSDate *modifiedDate = resourceValues[cacheContentDateKey];
        // 过期了
        if (expirationDate && [[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
            [urlsToDelete addObject:fileURL];
            continue;
        }

如果这个属性存在,并且和我们饿过期时间比较,如果文件的属性和过期时间比较,因为过期时间是根据当前时间算的,根据时间差算的,所以如果文件的修改时间比当前过期时间小的话,说明过期时间的定位的那个时间点跨度没有包含文件的修改时间,也就是说时间间隔太短了,换句话说也就是过期了,要不然肯定会把修改时间包含进去,然后就把这个文件放到数组跳过,

// Store a reference to this file and account for its total size.
        // 将文件总大小存储下来
        NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
        currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
        cacheFiles[fileURL] = resourceValues;

紧接着计算文件的总大小,后面用得到


// 将过期文件删除
    for (NSURL *fileURL in urlsToDelete) {
        [self.fileManager removeItemAtURL:fileURL error:nil];
    }

将上面的过期文件删除

  NSUInteger maxDiskSize = self.config.maxDiskSize;
    
    // 如果当前磁盘存储文件的总大小大于配置的大小
    if (maxDiskSize > 0 && currentCacheSize > maxDiskSize) {
        // Target half of our maximum cache size for this cleanup pass.
        const NSUInteger desiredCacheSize = maxDiskSize / 2;
        
        // Sort the remaining cache files by their last modification time or last access time (oldest first).
        // 将剩余的文件按照访问时间进行排序,最老的放在最前面
        NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                                 usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                     return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                 }];
        
        // Delete files until we fall below our desired cache size.
        for (NSURL *fileURL in sortedFiles) {
            if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;
                // 挨个删除,知道大小达到我们预期的值,也就是设置的最大值的一半
                if (currentCacheSize < desiredCacheSize) {
                    break;
                }
            }
        }
    }

这段代码的意思就是取出配置设置的磁盘允许缓存的总的文件大小,因为已经删除过期的文件了,接下来就是删除超出总大小的文件,这里是将剩下的文件按照时间排序,然后遍历剩下的文件,将最老的文件删除,然后拿出删除额这个文件的大小,将总大小减去这个大小和配置文件大小作比较,这里面有个策略,就是超出总大小之后,删除配置设置的总大小的一一半,也就是说每次留下配置设置的总大小缓存额一半。

// 获取存储的文件的总大小
- (NSUInteger)totalSize {
    NSUInteger size = 0;
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    for (NSString *fileName in fileEnumerator) {
        NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
        NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
        size += [attrs fileSize];
    }
    return size;
}
// 获取缓存的总个数
- (NSUInteger)totalCount {
    NSUInteger count = 0;
    NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
    count = fileEnumerator.allObjects.count;
    return count;
}

这里就是获取文件的总大小和总数量

// 移动缓存
- (void)moveCacheDirectoryFromPath:(nonnull NSString *)srcPath toPath:(nonnull NSString *)dstPath {
    NSParameterAssert(srcPath);
    NSParameterAssert(dstPath);
    // Check if old path is equal to new path
    if ([srcPath isEqualToString:dstPath]) {
        return;
    }
    BOOL isDirectory;
    // Check if old path is directory
    if (![self.fileManager fileExistsAtPath:srcPath isDirectory:&isDirectory] || !isDirectory) {
        return;
    }
    // Check if new path is directory
    if (![self.fileManager fileExistsAtPath:dstPath isDirectory:&isDirectory] || !isDirectory) {
        if (!isDirectory) {
            // New path is not directory, remove file
            [self.fileManager removeItemAtPath:dstPath error:nil];
        }
        NSString *dstParentPath = [dstPath stringByDeletingLastPathComponent];
        // Creates any non-existent parent directories as part of creating the directory in path
        if (![self.fileManager fileExistsAtPath:dstParentPath]) {
            [self.fileManager createDirectoryAtPath:dstParentPath withIntermediateDirectories:YES attributes:nil error:NULL];
        }
        // New directory does not exist, rename directory
        [self.fileManager moveItemAtPath:srcPath toPath:dstPath error:nil];
    } else {
        // New directory exist, merge the files
        NSDirectoryEnumerator *dirEnumerator = [self.fileManager enumeratorAtPath:srcPath];
        NSString *file;
        while ((file = [dirEnumerator nextObject])) {
            [self.fileManager moveItemAtPath:[srcPath stringByAppendingPathComponent:file] toPath:[dstPath stringByAppendingPathComponent:file] error:nil];
        }
        // Remove the old path
        [self.fileManager removeItemAtPath:srcPath error:nil];
    }
}

这个就是文件路径的移动切换,也很简单

问题总结

 为什么用 nscache,和 mapTable,我的疑问是,为什么nscache和maptable一起使用,maptable是弱引用

额,那么nscache和maptable都存储一份,那么nacache里面的经过系统自动清理缓存之后,maptable中不也

清除了吗?有什么用?为什么要这样用,用一个maptable不也能解决吗,带着这个问题,我在一些 ios 技术

讨论群里面抛出了这个问题,最后有一个兄弟的话点醒了我,因为可能会存在这种场景,比如我们的

nsacache和maptable都存储一张图片,界面上的imageview也用了这张图片,对这张图片进行强引用,当

nscache中的数据被清理之后,如果下次再来照这张图片,那么如果没有maptable二次存储,这时候内存缓存就不能命中了,就需要去磁盘缓存中取了,有了这个弱引用存储,如若nacache中找不到,那么就去maptable中找,如果找到了,就nscache再设置一次,这样就增加了命中率,提高了效率,不用去磁盘中取了,说到这里,是不是豁然开朗,但是,我还是有疑问,为什么不只用maptable一个数据结构去存储呢?为什么存储两份?

相关文章

网友评论

      本文标题:SDWebImage 源码分析之 SDMemoryCache 和

      本文链接:https://www.haomeiwen.com/subject/pnbccktx.html