1、枚举类
枚举 SDWebImageOptions 不同状态
枚举列出不同的下载选项 (选项使用掩码形式,如 1 << 2 表示将1左移2位,即: 00000010,也就是2。)
/**
枚举,定义了图片加载处理过程中的选项
*/
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/*
默认情况下,当一个URL下载失败的时候,这个URL会被加入黑名单列表,下次再有这个url的请求则停止请求。
如果为true,这个值表示需要再尝试请求。
*/
SDWebImageRetryFailed = 1 << 0,
/*
默认情况下,当UI可以交互的时候就开始加载图片。这个标记可以阻止这个时候加载。
而是当UIScrollView开始减速滑动的时候开始加载。
*/
SDWebImageLowPriority = 1 << 1,(表示1)
/*
这个属性禁止磁盘缓存
*/
SDWebImageCacheMemoryOnly = 1 << 2,(表示2))
/*
这个标记允许图片在加载过程中显示,就像浏览器那样。
默认情况下,图片只会在加载完成以后再显示。
*/
SDWebImageProgressiveDownload = 1 << 3,
/*
*即使本地已经缓存了图片,但是根据HTTP的缓存策略去网络上加载图片。也就是说本地缓存了也不管了,尝试从网络上加载数据。但是具体是从代理加载、HTTP缓存加载、还是原始服务器加载这个就更具HTTP的请求头配置。
*使用NSURLCache而不是SDWebImage来处理磁盘缓存。从而可能会导致轻微的性能损害。
*这个选项专门用于处理,url地址没有变,但是url对于的图片数据在服务器改变的情况。
*如果一个缓存图片更新了,则completion这个回调会被调用两次,一次返回缓存图片,一次返回最终图片。
*我们只有在不能确保URL和他对应的内容不能完全对应的时候才使用这个标记。
*/
SDWebImageRefreshCached = 1 << 4,
/*
当应用进入后台以后,图片继续下载。应用进入后台以后,通过向系统申请额外的时间来完成。如果时间超时,那么下载操作会被取消。(iOS 8 以后系统可以给 3 分钟的时间继续后台任务,后台时间到了,如果操作没有完成,也会取消操作)
*/
SDWebImageContinueInBackground = 1 << 5,
/*
处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
*/
SDWebImageHandleCookies = 1 << 6,
/*
*允许非信任的SSL证书请求。
*在测试的时候很有用。但是正式环境要小心使用。
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/*
* 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
*/
SDWebImageHighPriority = 1 << 8,
/*
默认情况下,在图片加载的过程中,会显示占位图。
但是这个标记会阻止显示占位图直到图片加载完成。
*/
SDWebImageDelayPlaceholder = 1 << 9,
/*
*默认情况下,我们不会去调用`animated images`(估计就是多张图片循环显示或者GIF图片)的`transformDownloadedImage`代理方法来处理图片。因为大部分transformation操作会对图片做无用处理。
*用这个标记表示无论如何都要对图片做transform处理。
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/*
*默认情况下,图片再下载完成以后都会被自动加载到UIImageView对象上面。但是有时我们希望UIImageView加载我们手动处理以后的图片。
*这个标记允许我们在completion这个Block中手动设置处理好以后的图片。
*/
SDWebImageAvoidAutoSetImage = 1 << 11,
};
2、核心方法
单例类 SDWebImageManager 、 SDImageCache、SDWebImageDownloader SDWebImageManager核心方法:
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
/// 1.如果想预先下载图片,使用[SDWebImagePrefetcher prefetchURLs]取代本方法
/// 预下载图片是有很多种使用场景的,当我们使用SDWebImagePrefetcher下载图片后,之后使用该图片时就不用再网络上下载了。
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.
/// 2.XCode有时候经常会犯一些错误,当用户给url赋值了字符串的时候,XCode也没有报错,因此这里提供一种
/// 错误修正的处理
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
/// 3.防止参数的其他错误
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
/// 4.operation会被作为该方法的返回值,但operation的类型是SDWebImageCombinedOperation,是一个封装的对象,并不是一个NSOperation
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
/// 5.在图片的下载中,会有一些下载失败的情况,这时候我们把这些下载失败的url放到一个集合中去,
/// 也就是加入了黑名单中,默认是不会再继续下载黑名单中的url了,但是也有例外,当options被设置为
/// SDWebImageRetryFailed的时候,会尝试进行重新下载。
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
/// 6.会有两种情况让我们停止下载这个url指定的图片:
/// - url的长度为0
/// - options并没有选择SDWebImageRetryFailed(重新下载错误url)且这个url在黑名单之中
/// 调用完成Block,返回operation
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;
}
/// 7.排除了所有的错误可能后,我们就先把这个operation添加到正在运行操作的数组中
/// 这里没有判断self.runningOperations是不是包含了operation,
/// 说明肯定会在下边的代码中做判断,如果存在就删除operation
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
// 操作url
NSString *key = [self cacheKeyForURL:url];
/// 8.self.imageCache的queryCacheOperationForKey方法是异步的获取指定key的图片,
/// 但是这个方法的operation是同步返回的,也就是说下边的代码会直接执行到return那里。
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
/// (8.1).这个Block会在查询完指定的key的图片后调用,由` dispatch_async(self.ioQueue, ^{`
/// 这个可以看出,实在异步线程采用串行的方式在调用,任务在self.imageCache的ioQueue中一个一个执行,是线程安全的
/// (8.2).如果每次调用loadImage方法都会生成一个operation,如果我们想取消某个下载任务
/// 再设计上来说,只要把响应的operation.isCancelled设置为NO,那么下载就会被取消。
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
/// (8.3).代码来到这里,我们就要根据是否有缓存的图片来做出响应的处理
/// 如果没有获取到缓存图片或者需要刷新缓存图片我们应该通过网络获取图片,但是这里增加了一个额外的控制
/// 根据delegate的imageManager:shouldDownloadImageForURL:获取是否下载的权限,返回YES,就继续下载
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
/// (8.3.1).这里需要注意了,当图片已经下载了,Options又选择了SDWebImageRefreshCached
/// 就会触发一次completionBlock回调,这说明这个下载的回调不是只触发一次的
/// 如果使用了dispatch_group_enter和dispatch_group_leave就一定要注意了
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.
completedBlock(image, nil, cacheType, YES, url);
});
}
// download if no image or requested to refresh anyway, and download allowed by delegate
// 下载类枚举
SDWebImageDownloaderOptions downloaderOptions = 0;
// (8.3.2).这里是SDWebImageOptions到SDWebImageDownloaderOptions的转换
// 其实就是0 |= xxx
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) {
__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
}
// 发生错误就返回
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
// 除了下边这几种情况之外的情况则把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 {
// 如果是SDWebImageRetryFailed就在黑名单中移除,不管有没有
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
}
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];
[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.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
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];
}
}
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;
}
网友评论