SDWebImage学习
SDWebImage版本是:'4.2.2'
SDWebImage是iOS开发中常用的图片加载的库,能下载并缓存图片。这次就着重介绍SDWebImage的特色功能:下载与缓存。
UIImageView+WebCache:直接使用的类
SDWebImageManager:总的管理类,维护了SDWebImageDownloader和SDImageCache实例,是下载与缓存的桥梁
SDWebImageDownloader:图片的下载队列
SDWebImageDownloaderOperation:真正的图片下载请求
SDImageCache:图片的缓存
SDWebImage的大体流程:
下面介绍一下SDWebImage的下载及缓存思路:
下载
在SDWebImage中,图片的下载是由SDWebImageDownloader类来完成。
在UIImageView +WebCache
提供的接口方法中,真正进行下载逻辑的是
// ============== UIView+ WebCache.m ============== //
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
而这个方法是UIView+WebCache
分类中的。由于SDWebImage框架支持UIButton的下载图片,所以把下载方法放到父类UIView中最为合适。
SDWebImageManager
维护了SDWebImageDownloader和SDImageCache实例,是下载与缓存的桥梁。
在下载任务开始的时候,SDWebImageManager首选会去SDImageCache查询是否有缓存,有了就直接返回,没有就去SDWebImageDownloader进行下载,各司其职。
SDWebImageManager有这几个重要的属性
@property (strong, nonatomic, readwrite, nonnull) SDImageCache imageCache;//管理缓存
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader //下载器imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;//记录失效url的名单
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;//记录当前正在执行的操作
SDWebImageManager下载的入口方法:loadImageWithURL:options:progress:completed:
下面是他的实现:
- (id <SDWebImageOperation>)loadImageWithURL2:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
...
//在SDImageCache里查询是否存在缓存的图片
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}
//没有缓存图片
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//没有图片,刷新缓存
if (cachedImage && options & SDWebImageRefreshCached) {
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
...
//通过SDWebImageDownloader开始下载
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
} else if (error) {
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
//如果出错,就添加到下载错误的failedURLs数组中
if (error.code != NSURLErrorNotConnectedToInternet && ...) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//进行缓存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//下载成功,结束
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
@synchronized(operation) {
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}
} else if (cachedImage) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
SDWebImageDownloader
SDWebImageDownloader下载管理器是一个单例类,它主要负责图片的下载操作的管理,有下面几个关键属性:
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
@property (strong, nonatomic) NSOperationQueue *downloadQueue; //下载队列
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
downloadQueue: 来管理下载队列的,改队列在子线程中异步执行
barrierQueue: 是一个串行队列,在一个单一队列中顺序处理所有下载操作的网络响应,由于允许多个图片同时下载,因此可能会有多个线程同时操作URLCallbacks属性。为了保证URLCallbacks操作(添加、删除)的线程安全性,SDWebImageDownloader将这些操作作为一个个任务放到barrierQueue队列中。
URLOperations:是NSMutableDictionary来防止下载未完成时,重复添加到下载队列中
既然是管理类,那么下载开始,暂停,取消都是支持的,分别是以下的方法。
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
- (void)setSuspended:(BOOL)suspended;
- (void)cancelAllDownloads;
下载:入口方法:downloadImageWithURL:options:progress:completed:
,方法内部通过SDWebImageDownloaderOperation
来进行真正的下载。下载来时先到URLOperations字典中看有没有已经存在的下载操作,没有才进行创建SDWebImageDownloaderOperation
添加到URLOperations
中,并添加到downloadQueue
进行队列管理。
取消单个下载:根据url取出SDWebImageDownloaderOperation
,并执行对应的取消方法:- (BOOL)cancel:(nullable id)token;
,然后把操作移出字典URLOperations
。
取消全部:直接调用队列downloadQueue
的cancelAllOperations
取消全部的方法。
暂停:设置self.downloadQueue.suspended
状态。
SDWebImageDownloaderOperation
SDWebImageDownloaderOperation类,它继承自NSOperation,,通过NSURLSession
的子类NSURLSessionDataTask
下载图片,这是iOS新的Api,老版的是通过NSURLConnection
(iOS7.0以前可用)下载的。如果设置了后台也进行下载SDWebImageDownloaderContinueInBackground
那么会通过UIBackgroundTaskIdentifier
来进行相关设置。
//TODO
缓存
SDWebImage提供了对图片缓存的支持,这样下次再去获取同一张图片时,可以直接从本地获取,而不再从远程服务器获取。而该功能是由SDImageCache类来完成的。
SDImageCache包含了内存缓存和磁盘缓存。:内存缓存和磁盘缓存。内存缓存由AutoPurgeCache(NSCache的子类)管理。磁盘缓存是NSFileManager来管理。
SDImageCache定义了一个串行队列dispatch_queue_t ioQueue
,来异步存储图片。
存储方法:
- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key toDisk:(BOOL)toDisk completion:(nullable SDWebImageNoParamsBlock)completionBlock;
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;
存储图片时以图片Url的MD5值作为key进行存储,存储的基础方法是- (void)storeImage: imageData: forKey: toDisk: completion:
实现如下:如果设置是需要缓存在内存,就缓存在内存中,再就是存储在沙盒中。
查询图片
在内存或磁盘中查询是否有key指定的图片,则可以分别使用以下方法:
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
删除图片
缓存的删除主要分成两种:单个和全部
通过key来删除单张图片
下面该方法是主要的方法,通过fromDisk的值来决定删除内存缓存还是删除所有的缓存(包括内存和沙盒)。通过completion来完成删除后的事项。
其中clearMemory
是清理内存缓存,系统注册了UIApplicationDidReceiveMemoryWarningNotification
通知,在内存警告的时候,会主动清理内存缓存。而方法- (void)clearDiskOnCompletion:
则是根据缓存的有效期以及最大的缓存大小进行清理。缓存有效期:根据maxCacheAge设置,默认为一周;最大缓存空间大小:根据maxCacheSize设置。缓存总大小超过这个值得时候,就会根据文件文件最后修改的时间倒序来进行移除。
#pragma mark - Remove Ops
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;
#pragma mark - Cache clean Ops
- (void)clearMemory;
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;
//TODO
总结
学习SDWebImage能学到:
- dispatch_barrier_sync函数:确保在执行完任务后才会执行后续操作。该方法常用于确保类的线程安全性操作
- @synchronized:多线程访问数组,保证同一时间只有一个线程在操作
- NSMutableURLRequest:用于创建一个网络请求对象,我们可以根据需要来配置请求报头等信息。
- NSOperation及NSOperationQueue:操作队列是Objective-C中一种高级的并发处理方法,它是基于GCD来实现的。相对于GCD来说,操作队列的优点是可以取消在任务处理队列中的任务,另外在管理操作间的依赖关系方面也容易一些。对SDWebImage中我们就看到了如何使用依赖将下载顺序设置成后进先出的顺序
- NSURLSession:用于网络请求及响应处理。是苹果在iOS7.0后推出了一套新的网络请求接口
- 开启一个后台任务。
- NSCache类:一个类似于集合的容器。它存储key-value对,这一点类似于NSDictionary类。我们通常用使用缓存来临时存储短时间使用但创建昂贵的对象。重用这些对象可以优化性能,因为它们的值不需要重新计算。另外一方面,这些对象对于程序来说不是紧要的,在内存紧张时会被丢弃。
- 清理缓存图片的策略:特别是最大缓存空间大小的设置。如果所有缓存文件的总大小超过这一大小,则会按照文件最后修改时间的逆序,以每次一半的递归来移除那些过早的文件,直到缓存的实际大小小于我们设置的最大使用空间。