iOS----------SDWebimage源码解析(2)

上一篇中我们介绍了常用的UIImageView使用SDWebimage加载图片的方法,遗留了问题,一个是在核心代码开始时要取消当前的operation,一个是下载图片返回operation的方法,下面我们重点解析这个图片的的下载方法。
我们点开源码发现,该方法在SDWebImageManager中,在常用的第三方框架中都会用到这个方法来管理一些事务。我们先看SDWebImageManager的.h文件。
文件开始时一系列枚举:

SDWebImageRetryFailed:图片下载失败重新下载
SDWebImageLowPriority:下载的优先级
SDWebImageCacheMemoryOnly:只进行内存缓存
SDWebImageProgressiveDownload:显示图片的下载进度
SDWebImageRefreshCached:刷新缓存
SDWebImageContinueInBackground:后台继续下载图片
SDWebImageHandleCookies:cookies处理方式
SDWebImageAllowInvalidSSLCertificates:接受无效的SSL证实
SDWebImageHighPriority:高优先级
SDWebImageDelayPlaceholder:延迟加载placeholder
SDWebImageTransformAnimatedImage:改变动画图片
SDWebImageAvoidAutoSetImage:    //当图片下载完以后要对下载后的图片进行处理,处理完成后再使用图片

代理:SDWebImageManagerDelegate
方法:

- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;

对象方法:

核心方法
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url  options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock
 completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

其他方法:

- (void)saveImageToCache:(UIImage *)image forURL:(NSURL *)url;
- (void)cancelAll;
- (BOOL)isRunning;
- (BOOL)cachedImageExistsForURL:(NSURL *)url;
- (BOOL)diskImageExistsForURL:(NSURL *)url;
- (void)cachedImageExistsForURL:(NSURL *)url
                     completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (void)diskImageExistsForURL:(NSURL *)url
                   completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (NSString *)cacheKeyForURL:(NSURL *)url;

在SDWebImageManager.m文件中,我们先来看这个核心的下载图片的方法,这个方法比较长,我们分为几部分来看

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {

    //整个block必须实现
    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.
    // 对url 进行处理
    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;
    }


    //创建一个SDWebImageCombinedOperation的operation
    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    //判断当前的url 是否包含在下载失败的url集合中,在访问failedURLs时要进行加锁,防止其他线程对其进行操作
    BOOL isFailedUrl = NO;
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

    //如果url在下载失败的url集合中并且不是下载失败重新下载(url的长度为0)那么抛出错误
    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;
    }

    //将operation加入到数组中
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

    //从缓存中根据url取出key(其实key就是url的string)
    NSString *key = [self cacheKeyForURL:url];
    .
    .
    .

在第一部分中创建了一个SDWebImageCombinedOperation的operation遵守了SDWebImageOperation协议,这个需要解惑这里为什么要使用这个方法来创建operation


@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
// 注意SDWebImageCombinedOperation遵循SDWebImageOperation,所以实现了cancel方法
// 在cancel方法中,主要是调用了cancelBlock,这个设计很值得琢磨
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
// 根据cacheType获取到image,这里虽然名字用的是cache,但是如果cache没有获取到图片
// 还是要把image下载下来的。此处只是把通过cache获取image和通过download获取image封装起来
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;
@end

第二部分:
这里第二部分涉及到从缓存中取图片的问题,因此调用了 SDImageCache类中的- (NSOperation )queryDiskCacheForKey:(NSString )key done:(SDWebImageQueryCompletedBlock)doneBlock 方法,我们随后在研究这个类中的东西,我看先看看如果manager中对这个方法中的block的处理,也就是结果的处理方法。
这里block中的代码又很长,我们仍然分块来看看
(1)先看这个判断,我们看后半部分(&&后面的)调用代理方法imageManager:shouldDownloadImageForURL:,你可以在源码中搜索这个方法,发现源码中没有实现这个方法,所以后面部分始终是YES,前面部分就是如果图片为nil或需要刷新内存,就执行下面的方法

        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {...

我先先看有image并且不需要刷新内存的情况

    ...
else if (image) {
            dispatch_main_sync_safe(^{
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                if (strongOperation && !strongOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
            //将该operation(SD_operation:自定义的operation)从数组中移除
            @synchronized (self.runningOperations) {
                [self.runningOperations removeObject:operation];
            }
        }
 else {
            // Image not in cache and download disallowed by delegate
            // 又没有从缓存中获取到图片,shouldDownloadImageForURL又返回NO,不允许下载,悲催!
            // 所以completedBlock中image和error均传入nil。
            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];
            }
        }

在看上面的没有图片或需要刷新缓存
(1)有图片并且需要刷新缓存,那么就在主线程刷新

//有图片 需要刷新缓存信息,那么在主线程中调用completedBlock传出image和获取image的途径
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);
                });
            }

(2)如果图片为空根据传入的options的状态,与SDWebImageDownloader类中的枚举进行同步

          // 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;
   //SDWebImageDownloaderIgnoreCachedResponse:因为SDWebImage有两种缓存方式,一个是SDImageCache,一个就是NSURLCache,所以知道为什么这个选项是Ignore了吧,因为已经从SDImageCache获取了image,就忽略NSURLCache了。
            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;
            }

(3)下面又调用了SDWebImageDownloader的对象方法来下载图片,这里又涉及到了SDWebImageDownloader这个类,这个类我们也以后再说我们先看得到图片后block中的操作。

上面是对下载图片失败的错误情况的处理,如果不是与网络相关方面的问题说明下载的图片本身的url有问题,那么将它加入到failedURLs中。说明改url时不能下载的url
    ...
                else {//如果当前url 为失败重试 将url从failedURLs移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }

                    //是否只能从内存中取出
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    //如果需要刷新缓存 并有image 单没有最新下载的image 则做操作
                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    }
                    //如果下载了image 并且需要对image进行处理,并且实现了图片处理的代理方法
                    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];
                            }
                            //主线程中同步调用completedBlock 将处理后的图片传出去
                            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];
                        }
                        //主线程中同步调用completedBlock 将处理后的图片传出去
                        dispatch_main_sync_safe(^{
                            if (strongOperation && !strongOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }

该block中处理对图片的处理也用到SDImageCache类。我们在下一篇中会介绍这个类。

其他东东:
SDImageCache:缓存类(manager的一个属性)
SDWebImageDownloader:下载类(manager的一个属性)
NSMutableSet *failedURLs:不能下载的图片的url集合
@property (strong, nonatomic) NSMutableArray *runningOperations;
:下载中的operation数组

SDWebImageCombinedOperation类遵守了SDWebImageOperation协议,重写了block的set方法和协议的cancel方法

@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
// 注意SDWebImageCombinedOperation遵循SDWebImageOperation,所以实现了cancel方法
// 在cancel方法中,主要是调用了cancelBlock,这个设计很值得琢磨
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;

// 根据cacheType获取到image,这里虽然名字用的是cache,但是如果cache没有获取到图片
// 还是要把image下载下来的。此处只是把通过cache获取image和通过download获取image封装起来
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSOperation *cacheOperation;

@end
@implementation SDWebImageCombinedOperation

- (void)setCancelBlock:(SDWebImageNoParamsBlock)cancelBlock {
    // check if the operation is already cancelled, then we just call the cancelBlock
    if (self.isCancelled) {
        if (cancelBlock) {
            cancelBlock();
        }
        _cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
    } else {
        _cancelBlock = [cancelBlock copy];
    }
}

- (void)cancel {
    self.cancelled = YES;
    if (self.cacheOperation) {
        [self.cacheOperation cancel];
        self.cacheOperation = nil;
    }
    if (self.cancelBlock) {
        self.cancelBlock();

        // TODO: this is a temporary fix to #809.
        // Until we can figure the exact cause of the crash, going with the ivar instead of the setter
//        self.cancelBlock = nil;
        _cancelBlock = nil;
    }
}

@end

总结:
这个类中主要是对图片下载和缓存方式的管理以及对下载好的图片、地址url等信息的block处理,承上启下的作用,承接了UIImageView+WebCache类,下接SDImageCache类和SDWebImageDownloader类。下篇中我们主要研究缓存的类(SDImageCache)。

遗留问题:
1、UIImageView+WebCache核心代码中开始停止所有的operation;
2、SDWebImageCombinedOperation类的原因好处以及出现的subOperation的原因
3、SDImageCache缓存类的实现方式
4、SDWebImageDownloader下载类的实现
我们会下面的几篇中逐步解决这些问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值