SDWebImage的使用
//设置图片,使用默认的下载策略,下载完成后,自动缓存到disk和内存中,图片填充到imageView上
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString]];
//设置图片,使用默认的下载策略,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//图片下载和填充完成后的回调,方便做一些其他操作
}];
//下载完成前,使用默认图片填充,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"]];
//下载完成前,使用默认图片填充,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//图片下载和填充完成后的回调,方便做一些其他操作
}];
//下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed 该下载策略,缓存数据
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed];
//下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed该下载策略,缓存数据
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//图片下载和填充完成后的回调,方便做一些其他操作
}];
//下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed该下载策略,缓存数据
[imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
//下载过程中的下载进度的回调,NSInteger receivedSize 已收到数据量, NSInteger expectedSize 总数据量
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//图片下载和填充完成后的回调,方便做一些其他操作
}];
SDWebImage的所有的下载策略
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**默认情况下,当一个URL下载失败,该URL会被加入黑名单,不会尝试从新下载。
*此标志禁用此黑名单。
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* This flag disable this blacklisting.
*/
SDWebImageRetryFailed = 1 << 0,
/**默认情况下,图像下载在UI交互期间启动,此标志禁用此功能。
导致例如UIScrollView减速的延迟下载
* By default, image downloads are started during UI interactions, this flags disable this feature,
* leading to delayed download on UIScrollView deceleration for instance.
*/
SDWebImageLowPriority = 1 << 1,
/**此标志禁用磁盘缓存
* This flag disables on-disk caching
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**此标志启用渐进式下载,图像将在下载期间逐步显示,就像浏览器那样
默认情况下,只有在下载完成后才会显示图像
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
*/
SDWebImageProgressiveDownload = 1 << 3,
/**即使图片缓存,也要遵循HTTP响应缓存控制,如果需要,可以从远程位置刷新图像。
磁盘缓存将由NSURLCache而不是SDWebImage处理,导致轻微的性能降低。
此选项可帮助处理在同一请求网址之后更改的图片,例如Facebook图形api配置文件图片。
如果缓存的图片刷新,那么completion block将会在缓存图片上调用一次,最终图片上调用一次
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
*/
SDWebImageRefreshCached = 1 << 4,
/** *在iOS 4+中,如果应用程序转到后台,请继续下载图像。这是通过询问系统来实现的
*需要额外的时间在后台让请求完成。如果后台任务过期,操作将被取消。
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageContinueInBackground = 1 << 5,
/**
*处理存储NSHTTPCookieStore中的cookie通过设置
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageHandleCookies = 1 << 6,
/**
*启用以允许不受信任的SSL证书。
*用于测试目的。在生产中小心使用。
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
*默认情况下,图像按照它们排队的顺序加载。此标志将它们移动到
*队列的前面。
* By default, images are loaded in the order in which they were queued. This flag moves them to
* the front of the queue.
*/
SDWebImageHighPriority = 1 << 8,
/** *默认情况下,在加载图像时加载占位符图像。此标志将延迟加载
*的占位符图像,直到图像完成加载。
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
*/
SDWebImageDelayPlaceholder = 1 << 9,
/**
*我们通常不会在动画图像上调用transformDownloadedImage委托方法,
*因为大多数转换代码会歪曲它。
*使用此标志来转换它们。
* We usually don't call transformDownloadedImage delegate method on animated images,
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/**
*默认情况下,图像在下载后添加到imageView。但在某些情况下,我们想
*在设置图像之前处理(应用一个过滤器或者用交叉渐变动画添加它)
*如果您想在成功时手动设置映像在完成中,请使用此标志
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/
SDWebImageAvoidAutoSetImage = 1 << 11
};
SDWebImage实现逻辑分析
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
//取消当前正在进行的下载,避免重复下载
[self sd_cancelCurrentImageLoad];
//使用运行时,使imageURLKey和self关联起来
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//如果不是延迟加载Placeholder的下载策略,就先把Placeholder显示在imageView上
if (!(options & SDWebImageDelayPlaceholder)) {
//该方法保证线程安全,更新UI的操作在主线程中完成
dispatch_main_async_safe(^{
self.image = placeholder;
});
}
//如果存在url,做相应处理,如果不存在,下载完成回调中返回错误信息
if (url) {
//如果要求显示加载中的小风火轮,那么添加该视图
// check if activityView is enabled or not
if ([self showActivityIndicatorView]) {
[self addActivityIndicator];
}
__weak __typeof(self)wself = self; //对self的若引用,避免循环引用的问题
//SDWebImageManager的方法获取图片
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
//移除小风火轮
[wself removeActivityIndicator];
if (!wself) return;
//在主线程中操作,保证线程安全
dispatch_main_sync_safe(^{
if (!wself) return;
//如果有图片,下载策略是避免自动设置图片,并且有completedBlock,那么实现该block,并返回
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
{
completedBlock(image, error, cacheType, url);
return;
}
//如果有图片,那么自动填充图片
else if (image) {
wself.image = image;
[wself setNeedsLayout];
} else {
//如果下载策略是延迟加载Placeholder,那么先填充Placeholder。也就是说该下载策略下,Placeholder图片的填充是在取得图片后(可能是下载的,也可能是从缓存或者是Disk中取得)
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
//如果有completedBlock,并且取照片的行为已完成,那么调用completedBlock
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
//设置图像加载操作(存储在基于UIView的字典中)
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
//移除风火轮
[self removeActivityIndicator];
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
//实现下载完成回调
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
总结一下:
主体逻辑
- 取消正在进行的下载,避免重复下载
- 使用运行时特性,把imageURLKey和self关联起来
- 如果不是延迟加载Placeholder的下载策略,就先把Placeholder显示在ImageView上
- 判断url是否存在,不存在,实现结束block,将失败原因作为参数返回
- url存在,进行系列操作,逻辑如下
URL存在的逻辑
- 判断是否加加载的小风火轮view
- 生成一个遵循<SDWebImageOperation> 的线程操作,用过来获取图片
- 在获取图片完成得回调里,调用相关completedBlock
- 设置图片加载操作(存储基于UIView的字典中)
下载图片downloadImage
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//是否是下载失败的url
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
//如果url不存在或者是,非忽略黑名单的黑名单上的url,返回错误信息和空图片
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}
//将该线程操作加入到正在运行的线程数组中去(保证线程安全)
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//把url作为key,查询缓存中是否有这个数据
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
//查询结束的回调
//如果操作被取消,将该线程操作从正在进行的线程操作列表移除,返回
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
//如果,图像不存在或者是需要更新缓存,并且代理响应了相应的下载方法。进入此if句
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果图片存在,并且缓存是SDWebImageRefreshCached
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image如果图片在缓存中存在,但是需要更新缓存,通知这个缓存图片
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. 尝试从新下载图片以便NSURLCache能从服务器上更新图片
completedBlock(image, nil, cacheType, YES, url);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate 如果图片不存在或者是需要更新图片并且代理允许下载
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (image && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing 如果图片缓存已经存在但要强制更新,强制逐步下载停止
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing 忽略缓存图片
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//开启一个下载图片的线程
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
//下载完成的block
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled如果该线程取消,我们什么也不做
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data 如果我们将调用completedBlock,另一个completedBlock和这个为同一对象的结束block,存在竞争条件。因此,如果这个方法被调用两次,我们将覆盖新数据
}
//下载出错的情况
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@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 && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block 图像刷新命中NSURLCache缓存,不调用完成块
}
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];
//将图片存到内存、disk
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
else {
//没有改变的图片,存储到本地缓存
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}
//下载完成得回调
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
}
//如果线程已完成,把该线程从正在运行的线程列表中移除
if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
//主operation取消时,副operation也要取消
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
//如果图片存在,进入此if句
else if (image) {
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
//把该线程从正在运行的线程列表中移除
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
//如果,图片不存在,并且self的代理也不响应相应的下载方法,进入此if句
else {
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];
return operation;
}
SDWebImage的下载总结
主体逻辑
- 生成新的operation
- 判断url是否为空,如果url为空或者是url在不忽略黑名单策略的黑名单里,返回,有错误信息的completedBlock,
- 把该operation加入正在运行的operation列表中
- 从缓存中查询以该url为key的图
- 在查询结束的block里做相应的操作
- 返回 operation
从缓存中查询以该url为key的图的逻辑
- 从内存中查询,有,返回图片
- 内存中无,查询disk,有,返回图片
- disk 中无,返回nil
在查询结束的block里做的操作
- 如果operation被取消,将改operation从正在运行的operation列表中删除为,返回
- Image不存在或者需要更新缓存(options & SDWebImageRefreshCached),并且self的代理响应(imageManager:shouldDownloadImageForURL:)方法时,开始下载图片,在下载完成的回调里:根据缓存策略,缓存内存或者是disk;根据错误提示和缓存策略,添加黑名单
- Image存在,在完成block里返回Image,将将改operation从正在运行的operation列表中删除为
- 其他情况,在完成block里返回nil,将将改operation从正在运行的operation列表中删除为
SDWebImage的下载策略
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
/*低优先级下载
*/
SDWebImageDownloaderLowPriority = 1 << 0,
/*逐步下载
*/
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**默认情况下,请求不使用NSURLCache的缓存策略,该选项NSURLCache作为默认的策略被使用
* By default, request prevent the use of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**忽略缓存,直接从远端下载
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**在iOS 4+的系统,如果设置了允许后台下载,该选项可以在程序后代的情况下,继续下载
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理 cookie在NSHTTPCookieStore上的缓存
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**允许非信任安全证书的请求。
用做测试的目的。实际生产环境谨慎使用
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**高优先级下载
* Put the image in the high priority queue.
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};