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

原创粉丝点击