SDWebImage源码学习之由浅入深 二
来源:互联网 发布:玄空排盘软件手机版 编辑:程序博客网 时间:2024/06/05 11:03
接上篇分析SDWebImage源码学习之由浅入深 一
if (url) { // check if activityView is enabled or not if ([self sd_showActivityIndicatorView]) { [self sd_addActivityIndicator]; } __weak __typeof(self)wself = self; id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { __strong __typeof (wself) sself = wself; [sself sd_removeActivityIndicator]; if (!sself) { return; } dispatch_main_async_safe(^{ if (!sself) { return; } if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) { completedBlock(image, error, cacheType, url); return; } else if (image) { [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } else { if ((options & SDWebImageDelayPlaceholder)) { [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock]; [sself sd_setNeedsLayout]; } } if (completedBlock && finished) { completedBlock(image, error, cacheType, url); } }); }]; [self sd_setImageLoadOperation:operation forKey:validOperationKey]; } else { dispatch_main_async_safe(^{ [self sd_removeActivityIndicator]; if (completedBlock) { NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}]; completedBlock(nil, error, SDImageCacheTypeNone, url); } }); }
url 不存在情况比较简单,移除加载圈,如果有回调,则回到一些错误信息等。
url 存在的情况:
if ([self sd_showActivityIndicatorView]) { [self sd_addActivityIndicator]; }
这需要使用者自己设置,是否在加载时显示加载圈。
下面需要先分析SDWebImageManager
+ (nonnull instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance;}- (nonnull instancetype)init { SDImageCache *cache = [SDImageCache sharedImageCache]; SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; return [self initWithCache:cache downloader:downloader];}
上述内容主要是UI显示层的操作,这里可以说是开始进入了SDWebImage的核心部分。
在sharedManager 中主要是完成了缓存类和下载类的初始化,及一些容器初始化
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader { if ((self = [super init])) { _imageCache = cache; _imageDownloader = downloader; _failedURLs = [NSMutableSet new]; // 不会重复存储 _runningOperations = [NSMutableArray new]; } return self;}
在这里先持有cache和downloader,并创建了一个存储失败URL 的容器 _failedURLs 和一个存储正在运行的线程数组 _runningOperations。
先查看SDImageCache 的实现
+ (nonnull instancetype)sharedImageCache { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance;}- (instancetype)init { return [self initWithNamespace:@"default"];}- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns { NSString *path = [self makeDiskCachePath:ns]; return [self initWithNamespace:ns diskCacheDirectory:path];}- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace { NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); return [paths[0] stringByAppendingPathComponent:fullNamespace];}
首先是在Disk 中创建了一个default的存储空间(就是创建了一个缓存文件存储路径)
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns diskCacheDirectory:(nonnull NSString *)directory { if ((self = [super init])) { NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns]; // Create IO serial queue _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL); _config = [[SDImageCacheConfig alloc] init]; // Init the memory cache _memCache = [[AutoPurgeCache alloc] init]; _memCache.name = fullNamespace; // Init the disk cache if (directory != nil) { _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace]; } else { NSString *path = [self makeDiskCachePath:ns]; _diskCachePath = path; } dispatch_sync(_ioQueue, ^{ _fileManager = [NSFileManager new]; });#if SD_UIKIT // Subscribe to app events [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deleteOldFiles) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundDeleteOldFiles) name:UIApplicationDidEnterBackgroundNotification object:nil];#endif } return self;}
SDImageCacheConfig类是对缓存做配置,是否解码,存储周期等信息
AutoPurgeCache类 只是实现了一个监听UIApplicationDidReceiveMemoryWarningNotification 内存报警时,移除全部。
实现文件管理后再添加监听
UIApplicationDidReceiveMemoryWarningNotification 执行方法clearMemory
UIApplicationWillTerminateNotification 执行方法deleteOldFiles
UIApplicationDidEnterBackgroundNotification 执行方法backgroundDeleteOldFiles
- (void)clearMemory { [self.memCache removeAllObjects];}
可见当收到内存警告时,直接将缓存的全部内容清空
- (void)deleteOldFiles { [self deleteOldFilesWithCompletionBlock:nil];}- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock { dispatch_async(self.ioQueue, ^{ NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES]; NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey]; // This enumerator prefetches useful properties for our cache files. NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL includingPropertiesForKeys:resourceKeys options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:NULL]; NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge]; 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; 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 *modificationDate = resourceValues[NSURLContentModificationDateKey]; if ([[modificationDate 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) { [_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. if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) { // Target half of our maximum cache size for this cleanup pass. const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2; // Sort the remaining cache files by their last modification time (oldest first). NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent usingComparator:^NSComparisonResult(id obj1, id obj2) { return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]]; }]; // Delete files until we fall below our desired cache size. for (NSURL *fileURL in sortedFiles) { if ([_fileManager removeItemAtURL:fileURL error:nil]) { NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL]; NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey]; currentCacheSize -= totalAllocatedSize.unsignedIntegerValue; if (currentCacheSize < desiredCacheSize) { break; } } } } if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(); }); } });}
当程序进入后台和程序结束时执行上述方法。略有差别的是,当程序进入后台会申请时间处理清理过程。
具体清理策略是,先清理已过期的文件,如果设置了最大缓存空间(默认没设置,等于0),当超出缓存空间时间,先将缓存内容按时间排序,从最久远的时间开始删除,一致删到 安全空间值(最大缓存空间的一半)
下篇分析 SDWebImageDownloader
- SDWebImage源码学习之由浅入深 二
- SDWebImage源码学习之由浅入深一
- SDWebImage源码解析(二)
- SDWebImage 源码阅读(二)
- SDWebImage源码剖析(二)
- 由浅入深学习JavaScript(二)
- Spring源码由浅入深系列二 类结构
- 解读SDWebImage源码之UIImageView
- SDWebImage源码解读 之SDWebImageCompat
- SDWebImage源码解读 之SDWebImageDecoder
- SDWebImage源码解读 之SDWebImageDownloaderOperation
- SDWebImage源码解读 之SDWebImageDownloader
- SDWebImage源码解读之 SDWebImageManager
- SDWebImage源码解读 之SDWebImagePrefetcher
- SDWebImage源码解读之 分类
- SDWebImage源码解读之分类
- Android:Dagger2学习之由浅入深
- iOS SDWebImage源码研究(二)
- 基于zookeeper实现分布式锁
- EhCache spring
- 程序员面试、算法研究、编程艺术、红黑树、机器学习5大系列集锦
- 123
- synchronized关键字
- SDWebImage源码学习之由浅入深 二
- TextBox控件自定义样式
- 接口与实现:实验2
- iPhone X(10)屏幕分辨率与适配
- Activemq 的topic总结
- poj 3164 通信网络 最小树状图
- SpringBoot项目导入外部服务器
- 按钮改变文字大小颜色
- HHUOJ_1349: 分割排序