SDWebImage源码解读

栏目: IOS · 发布时间: 5年前

内容简介:SDWebImage是一个开源的第三方库,它提供了UIImageView的分类来实现从网络端下载数据并缓存到内存和磁盘。SDWebImage有如下特点:一.储备知识

SDWebImage是一个开源的第三方库,它提供了UIImageView的分类来实现从网络端下载数据并缓存到内存和磁盘。

SDWebImage有如下特点:

  • 提供了UIImageView和UIButton的分类。以支持加载网络图片并缓存。

  • 一个异步的图片下载器

  • 提供异步的内存和磁盘缓存,并自动处理缓存过期。

  • 后台图片解压缩处理。

  • 确保同一个URL不会被下载多次。

  • 确保主线程永远不会阻塞。

一.储备知识

SDWebImage中每一个下载任务都是一个SDWebImageDownloaderOperation,而SDWebImageDownloaderOperation又是继承自NSOperation,所以每一个下载任务对应一个NSOperation。在SDWebImage中使用SDWebImageDownloader来管理

多个下载任务,在SDWebImageDownloader中有一个downloadedQueue这个属性,这个属性是NSOperationQueue类型的,也就是用NSOperationQueue来管理NSOperation。

下面我们就一起学习一下NSOperation和NSOperationQueue。

NSOperation和NSOperationQueue

NSOperation是一个抽象类,用来表示与单个任务相关联的代码和数据。

NSOperation类是一个抽象类,我们不能直接去使用它而是应该创建一个子类来继承它。虽然它只是一个抽象类,但是它的基本实现还是提供了很有价值的逻辑来确保任务的安全执行。这种原生的逻辑可以让我们专注于任务的真正的实现,而不需要用一些胶水代码去确保这个任务能正常工作在其他地方。

我们可以把一个NSOperation对象加入到一个operation queue中,让这个operation queue去决定什么时候执行这个operation。 当使用operation queue去管理operation时,轮到某个operation执行时实际是去执行这个operation的start方法,所以我们一个operation对象实际要执行的任务应该放在start方法里面。 如果我们不想使用operation queue,也可以通过手动调用NSOperation的start方法来执行任务。

我们可以通过添加依赖来确定operation queue中operation的具体执行顺序。添加依赖和移除依赖可以使用下列的API:

//添加依赖
- (void)addDependency:(NSOperation *)op;
//移除依赖
- (void)removeDependency:(NSOperation *)op;

只要当一个operation对象的所有依赖都执行完成的时候,其才可以变成熟ready状态,然后才可以被执行。如果一个operation没有添加依赖,直接加入了operation queue中,那么就会按照加入队列的先后顺序,当这个operation的前一个operation执行完成以后,其状态才会变成ready,才能被执行。

NSOperation对象有下列四个比较重要的状态:

  • isCancelled

  • isExecuting

  • isFinished

  • isReady

其中isExecuting, isFinished, isReady这三种状态相当于是operation对象的生命周期:

SDWebImage源码解读

而isCancelled这种状态则比较特殊,当我们对operation对象调用- (void)cancel方法时,其isCancelled属性会被置为YES。这个时候要分两种情况:

  • operation正在执行

也就是说其状态现在是isExecuting,调用- (void)cancel方法后会马上停止执行当前任务,并且状态变为isFinished,isCancelled = Yes。

  • operation还没开始执行

这个时候operation的状态其实是isReady这个状态之前,operation还没开始执行,调用- (void)cancel方法后会去调用operation的start方法,在start方法里我们要去处理cancel事件,并设置isFinished = YES。

SDWebImage源码解读

SDWebImageOptions

在SDWebImage中大量使用了option类型,通过判断option类型的值来决定下一步应该怎么做,所以如果对这些option值一点都不了解的话可能理解起源码来也会非常难受。SDWebImageOptions是暴露在外的可供使用者使用的option。还有一些option比如SDImageCacheOptions, SDWebImageDownloaderOptions这些都是不暴露给用户使用的。源码中是根据用户设置的SDWebImageOptions这个option来确定另外两个option的值。

下面我们来具体看一下SDWebImageOptions

SDWebImage源码解读

SDImageCacheOptions

SDWebImage源码解读

这里我们也可以看到,SDImageCacheOptions中的三个选项在SDWebImageOptions中都有对应的选项。

  • SDImageCacheQueryDataWhenInMemory对应SDWebImageQueryDataWhenInMemory。

  • SDImageCacheQueryDiskSync对应SDWebImageQueryDiskSync。

  • SDImageCacheScaleDownLargeImages对应SDWebImageScaleDownLargeImages。

SDWebImageDownloaderOptions

SDWebImage源码解读

SDWebImageDownloaderOptions中所有选项在SDWebImageOptions中也有相对应的选项,这里不再赘述。

SDImageCacheType

    //从缓存中得不到数据
    SDImageCacheTypeNone,
    
    //从磁盘缓存中得到数据
    SDImageCacheTypeDisk,
    
    //从内存缓存中得到数据
    SDImageCacheTypeMemory

框架的主要类和一次图片加载的主要流程

框架的主要类

SDWebImage源码解读

从上图也可以看出,整个框架主要分为三部分,即图片的下载,图片的缓存,和处理图片相关的类。

一次图片加载的主要流程

SDWebImage源码解读 针对上图中一次图片加载的主要流程,每一步做介绍:

1.SDWebImage为UIImagView创建了一个分类UIImageView (WebCache),然后UIImageView对象可以调用这个分类的方法来下载图片:

    [imageView sd_setImageWithURL:[NSURL URLWithString:@""]];

2.UIImageView (WebCache)的- (void)sd_setImageWithURL:(nullable NSURL *)url方法实际调用了UIView (WebCache)的下列方法:

- (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;

3.UIView (WebCache)的上述方法在实现时会创建一个SDWebImageManager的实例对象,然后调用其下列方法来加载图片:

- (id 
<sdwebimageoperation>
 )loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock;
</sdwebimageoperation>

4.在SDWebImageManager对象的上述方法里,首先会查询在缓存中有没有这个图片,然后根据各种option的判断决定是否要从网络端下载。查询缓存中有没有是通过调用SDImageCache对象的实例方法来实现的:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 
                                            options:(SDImageCacheOptions)options 
                                               done:(nullable SDCacheQueryCompletedBlock)doneBlock;

5.返回缓存查询的结果

6.如果需要下载图片,那么调用SDWebImageDownloader对象的下列方法进行下载:

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

7.获取从网络端下载的图片。

8.判断是否要将下载的图片进行缓存,如果需要,则缓存。

9.把通过SDWebImageManager对象获取的图片显示在UIImageView上。

源码分析

这一部分我们进行详细的源码分析。

首先从SDWebImageManager类的loadImageWithURL:方法看起:

由于代码比较长,我就采用注释的方式

- (id 
<sdwebimageoperation>
 )loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
    
    //如果传进来的是一个NSString,则把NSString转化为NSURL
    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 = [SDWebImageCombinedOperation new];
    operation.manager = self;
    
    //self.failedURLs是nsurl的黑名单,一般情况下,如果URL在这个黑名单里,那么就不会尝试加载这个图片,直接返回
    BOOL isFailedUrl = NO;
    if (url) {
        LOCK(self.failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        UNLOCK(self.failedURLsLock);
    }
    
    //SDWebImageRetryFailed即即使URL被加入了黑名单,也要尝试加载这个URL对应的图片
    //如果URL长度为0,或者URL被加入了黑名单并且没有设置SDWebImageRetryFailed,那么就直接回调完成的block
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    
    LOCK(self.runningOperationsLock);
    [self.runningOperations addObject:operation];
    UNLOCK(self.runningOperationsLock);
    NSString *key = [self cacheKeyForURL:url];
    
    //由于我们在使用API的时候只设置SDWebImageOptions,所以这里就是根据用户设置的SDWebImageOptions去设置SDImageCacheOptions
    SDImageCacheOptions cacheOptions = 0;
    if (options & SDWebImageQueryDataWhenInMemory) cacheOptions |= SDImageCacheQueryDataWhenInMemory;
    if (options & SDWebImageQueryDiskSync) cacheOptions |= SDImageCacheQueryDiskSync;
    if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
    
    __weak SDWebImageCombinedOperation *weakOperation = operation;
    //这里开始调用SDImageCache对象的queryCacheOperationForKey:方法去缓存中查找有没有这个URL对应的图片
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key options:cacheOptions done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        __strong __typeof(weakOperation) strongOperation = weakOperation;
        if (!strongOperation || strongOperation.isCancelled) {
            [self safelyRemoveOperationFromRunning:strongOperation];
            return;
        }
        
        // 判断我们是否需要从网络端下载图片
        //首先检查没有设置只能从缓存中获取,然后检查cachedImage = nil或者设置了要刷新缓存,则需要从网络端下载图片
        BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
        && (!cachedImage || options & SDWebImageRefreshCached)
        && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
        if (shouldDownload) {
            //从缓存中获取了图片并且设置了要刷新缓存这个option,则要进行两次完成的回调,这是第一次回调
            if (cachedImage && options & SDWebImageRefreshCached) {
                // 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.
                [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }
            
            // download if no image or requested to refresh anyway, and download allowed by delegate
            //这里是根据用户设置的SDWebImageOptions来手动设置SDWebImageDownloaderOptions
            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 (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
            
            //如果已经从缓存中获取了图片并且设置了要刷新缓存
            if (cachedImage && options & SDWebImageRefreshCached) {
                
                //这里其实就是把SDWebImageDownloaderProgressiveDownload这个option去掉
                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                // ignore image read from NSURLCache if image if cached but force refreshing
                //加上SDWebImageDownloaderIgnoreCachedResponse这个option,忽略NSURLCache中缓存的response
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            
            __weak typeof(strongOperation) weakSubOperation = strongOperation;
            
            //l开始进行图片的下载
            strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
                __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
                if (!strongSubOperation || strongSubOperation.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) {
                    [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock error:error url:url];
                    BOOL shouldBlockFailedURL;
                    // 后面都是判断在请求失败的情况下是否应该把
                    if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
                        shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
                    } else {
                        shouldBlockFailedURL = (   error.code != NSURLErrorNotConnectedToInternet
                                                && error.code != NSURLErrorCancelled
                                                && error.code != NSURLErrorTimedOut
                                                && error.code != NSURLErrorInternationalRoamingOff
                                                && error.code != NSURLErrorDataNotAllowed
                                                && error.code != NSURLErrorCannotFindHost
                                                && error.code != NSURLErrorCannotConnectToHost
                                                && error.code != NSURLErrorNetworkConnectionLost);
                    }
                    
                    if (shouldBlockFailedURL) {
                        LOCK(self.failedURLsLock);
                        [self.failedURLs addObject:url];
                        UNLOCK(self.failedURLsLock);
                    }
                }
                else {
                    //如果设置了SDWebImageRetryFailed那么就要把URL从黑名单中移除
                    if ((options & SDWebImageRetryFailed)) {
                        LOCK(self.failedURLsLock);
                        [self.failedURLs removeObject:url];
                        UNLOCK(self.failedURLsLock);
                    }
                    
                    //判断是否应该把下载的图片缓存到磁盘,SDWebImageCacheMemoryOnly这个option表示只把图片缓存到内存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
                    
                    // We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale.
                    if (self != [SDWebImageManager sharedManager] && self.cacheKeyFilter && downloadedImage) {
                        downloadedImage = [self scaledImageForKey:key image:downloadedImage];
                    }
                    
                    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), ^{
                            @autoreleasepool {
                                UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
                                
                                if (transformedImage && finished) {
                                    BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                    NSData *cacheData;
                                    // pass nil if the image was transformed, so we can recalculate the data from the image
                                    if (self.cacheSerializer) {
                                        cacheData = self.cacheSerializer(transformedImage, (imageWasTransformed ? nil : downloadedData), url);
                                    } else {
                                        cacheData = (imageWasTransformed ? nil : downloadedData);
                                    }
                                    [self.imageCache storeImage:transformedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                                }
                                
                                [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                            }
                        });
                    } else {
                        //可以直接看到这一部分
                        if (downloadedImage && finished) {
                            if (self.cacheSerializer) {
                                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                                    @autoreleasepool {
                                        NSData *cacheData = self.cacheSerializer(downloadedImage, downloadedData, url);
                                        [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key toDisk:cacheOnDisk completion:nil];
                                    }
                                });
                            } else {
                                //对图片进行缓存
                                [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                        }
                        //第二次调用完成的block
                        [self callCompletionBlockForOperation:strongSubOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongSubOperation];
                }
            }];
            //如果从从缓存中获取了图片并且不需要下载
        } else if (cachedImage) {
            //执行完成的回调
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:strongOperation];
        } else {
            // 缓存中没有获取图片,也不用下载
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:strongOperation];
        }
    }];
    
    return operation;
}
</sdwebimageoperation>

总结一下SDWebImageManager的loadImageWithURL:所做的事情:

其实在loadImageWithURL:里面做了加载图片的完整流程。首先检查传入的NSURL的有效性。然后开始从缓存中查找是否有这个图片,得到查询结果之后再根据查询结果和设置的option判断是否需要进行下载操作,如果不需要下载操作那么就直接使用cache image进行下载回调。如果需要进行下载操作那么就开始下载,下载完成后按照设置的option将图片缓存到内存和磁盘,最后进行完成的回调。

然后我们看一下查询缓存的具体过程,也就是SDImageCache这个类的queryCacheOperationForKey:方法:

这里也是采用注释的方式

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // 首先检查内存缓存中有没有这个图片,注意内存缓存使用的是NSCache,它是一个类字典结构,使用图片对应的nsurl作为key,在查询的时候就用这个key去查询
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    //是否只查询内存缓存(如果从内存缓存中获取了图片并且没有设置SDImageCacheQueryDataWhenInMemory,那么就只查询内存缓存)
    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            //执行回调
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    NSOperation *operation = [NSOperation new];
    void(^queryDiskBlock)(void) =  ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }
        
        @autoreleasepool {
            //从磁盘中查询
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            //j缓存获取的类型,有三种m类型,none,memory,disk
            SDImageCacheType cacheType = SDImageCacheTypeNone;
            if (image) {
                // 图片是从内存缓存中获取的
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData) {
                //图片是从磁盘缓存中获取的
                cacheType = SDImageCacheTypeDisk;
                // 解压图片
                diskImage = [self diskImageForKey:key data:diskData options:options];
                //判断是否需要把图片缓存到内存
                if (diskImage && self.config.shouldCacheImagesInMemory) {
                    NSUInteger cost = SDCacheCostForImage(diskImage);
                    //将图片缓存到内存
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            if (doneBlock) {
                if (options & SDImageCacheQueryDiskSync) {
                    doneBlock(diskImage, diskData, cacheType);
                } else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        doneBlock(diskImage, diskData, cacheType);
                    });
                }
            }
        }
    };
    
    if (options & SDImageCacheQueryDiskSync) {
        queryDiskBlock();
    } else {
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    
    return operation;
}

总结一下queryCacheOperationForKey:方法所做的事情:

SDImageCache这个类是专门负责缓存相关的问题的,包括查询缓存和将图片进行缓存。SDImageCache使用了一个NSCache对象来进行内存缓存,磁盘缓存则是把图片数据存放在应用沙盒的Caches这个文件夹下。

首先查询内存缓存,内存缓存查询完了以后再判断是否需要查询磁盘缓存。如果查询内存缓存已经有了结果并且没有设置一定要查询磁盘缓存,那么就不查询磁盘缓存,否则就要查询磁盘缓存。内存缓存没有查询到图片,并且磁盘缓存查询到了图片,那么就要把这个内容缓存到内存缓存中。

图片的缓存查询完成后我们再来看一下下载操作,即SDWebImageDownloader的downloadImageWithURL:方法

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }
    
    LOCK(self.operationsLock);
    NSOperation
<sdwebimagedownloaderoperationinterface>
  *operation = [self.URLOperations objectForKey:url];
    if (!operation || operation.isFinished) {
        
        //创建一下下载的operation
        operation = [self createDownloaderOperationWithUrl:url options:options];
        __weak typeof(self) wself = self;
        operation.completionBlock = ^{
            __strong typeof(wself) sself = wself;
            if (!sself) {
                return;
            }
            LOCK(sself.operationsLock);
            [sself.URLOperations removeObjectForKey:url];
            UNLOCK(sself.operationsLock);
        };
        [self.URLOperations setObject:operation forKey:url];
        // Add operation to operation queue only after all configuration done according to Apple's doc.
        // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
        
        //把operation加入到nNSOperationQueue中去
        [self.downloadQueue addOperation:operation];
    }
    UNLOCK(self.operationsLock);
    //这一部分代码是在取消operation的时候使用
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
    
    SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
    token.downloadOperation = operation;
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
    return token;
}
</sdwebimagedownloaderoperationinterface>

SDWebImageDownloader这个类是专门管理下载的,它有一个属性是downloadQueue,这是一个NSOperationQueue,每创建一个新的下载任务都把它加入到这个downloadQueue中,让downloadQueue去管理任务的开始,取消,结束。

上面的方法其实做的事情很简单,就是创建了一个下载图片的operation,然后把它加入到了downloadQueue中去。

下面我们来具体看一下创建下载图片的operation的过程,即SDWebImageDownloader类的createDownloaderOperationWithUrl:方法:

- (NSOperation
<sdwebimagedownloaderoperationinterface>
  *)createDownloaderOperationWithUrl:(nullable NSURL *)url
                                                                                  options:(SDWebImageDownloaderOptions)options {
    NSTimeInterval timeoutInterval = self.downloadTimeout;
    if (timeoutInterval == 0.0) {
        timeoutInterval = 15.0;
    }
    // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
    NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                cachePolicy:cachePolicy
                                                            timeoutInterval:timeoutInterval];
    
    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    request.HTTPShouldUsePipelining = YES;
    if (self.headersFilter) {
        request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
    }
    else {
        request.allHTTPHeaderFields = [self allHTTPHeaderFields];
    }
    
    //前面都是为了创建一个request,然后使用request和session对象去创建下载的operation
    NSOperation
 <sdwebimagedownloaderoperationinterface>
   *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
    operation.shouldDecompressImages = self.shouldDecompressImages;
    
    if (self.urlCredential) {
        operation.credential = self.urlCredential;
    } else if (self.username && self.password) {
        operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
    }
    
    //设置operation的队列优先级
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    
    //如果设置的执行顺序是xLIFI,即后进先出,则要把queue中的最后一个加入的operation的依赖设置为该operation,这样来保证这个operation最先执行
    if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
        [self.lastAddedOperation addDependency:operation];
        self.lastAddedOperation = operation;
    }
    return operation;
}
 </sdwebimagedownloaderoperationinterface>
</sdwebimagedownloaderoperationinterface>

这个方法就是创建了一个request对象,然后使用这个request对象和session对象去创建下载的operation对象。

我们看一下负责单个下载任务的operation对象到底是怎么创建的,即SDWebImageDownloaderOperation类的- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options方法:

- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
                              inSession:(nullable NSURLSession *)session
                                options:(SDWebImageDownloaderOptions)options {
    if ((self = [super init])) {
        _request = [request copy];
        _shouldDecompressImages = YES;
        _options = options;
        _callbackBlocks = [NSMutableArray new];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        _unownedSession = session;
        _callbacksLock = dispatch_semaphore_create(1);
        _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

这个初始化方法其实也很简单,就是给自己的成员变量赋值

我们知道,NSOperation类的真正执行任务是在其start方法里面,那么我们看一下SDWebImageDownloaderOperation的start方法的具体实现:

代码比较长,我在关键部分加了注释

- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }
#if SD_UIKIT
        //这一部分就是解决在后台仍然进行下载的问题
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        NSURLSession *session = self.unownedSession;
        //创建一个session对象,因为后面要创建NSURLSessionTask,需要session对象
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;
        }
        
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check
            NSURLCache *URLCache = session.configuration.URLCache;
            if (!URLCache) {
                URLCache = [NSURLCache sharedURLCache];
            }
            NSCachedURLResponse *cachedResponse;
            // NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
            @synchronized (URLCache) {
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        
        //创建dataTask,这个才是真正执行下载任务的
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
        if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
            if (self.options & SDWebImageDownloaderHighPriority) {
                self.dataTask.priority = NSURLSessionTaskPriorityHigh;
            } else if (self.options & SDWebImageDownloaderLowPriority) {
                self.dataTask.priority = NSURLSessionTaskPriorityLow;
            }
        }
#pragma clang diagnostic pop
        [self.dataTask resume];
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
        });
    } else {
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
        [self done];
        return;
    }
#if SD_UIKIT
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

这里就是通过一个session对象和一个request对象创建了一个dataTask对象,这个dataTask对象才是真正用来下载的,然后调用[self.dataTask resume]执行下载。

到这里SDWebImage的源码分析就结束啦。

参考:

SDWebImage实现分析

这篇文章在简书的地址: SDWebImage源码解读


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

全栈开发之道

全栈开发之道

和凌志 / 电子工业出版社 / 68.00元

全栈(Full Stack)是一种全新的以前端为主导的框架,框架选型聚焦在MEAN(MongoDB、Express、AngularJS、Node.js)上。选用MEAN全栈技术,可以快速地实现敏捷开发,尤其是到了产品的运营阶段,其优势表现得非常明显。本书主要介绍MEAN全栈技术,分为入门篇、基础篇和实战篇,入门篇对全栈进行了概述,基础篇重点介绍了全栈的四个主要技术,即MongoDB、Express......一起来看看 《全栈开发之道》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试