SDWebImage源码流程分析

流程概括

image.png

在使用SDWebImage时,我们总会先调用sd_setImageWithURL等一系列方法。然后我们一层一层的探索下去时发现,归根结底都是在调用UI赋值器中的sd_internalSetImageWithURL方法。这个方法的作用是利用图片加载器的loadImageWithURL方法,将加载好的图片赋值给UI。而图片加载器,则先利用存储器queryCacheOperationForKey查找图片,如果没有找到,就利用下载器downloadImageWithURL下载图片。存储器会先去缓存中读取,如果没找到,就从磁盘中读取(二级缓存)。下载器会把这些下载信息交给下载任务,并添加在下载队列里,最多有6个下载任务可以同时执行。小毅接下来将对流程牵扯到的这四个方法进行讲解。

一、 sd_internalSetImageWithURL

在URL正常的情况下,用加载器加载好的图片给UI赋值
1、程序主流程


image.png

2、源码解析

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

NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//如果没有设置Placeholder延迟赋值,那就先把placeholder赋值给UI
if (!(options & SDWebImageDelayPlaceholder)) {
    dispatch_main_async_safe(^{
        [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
    });
}

//如果URL有值
if (url) {
    //看看是否要显示ActivityIndicator
    if ([self sd_showActivityIndicatorView]) {
        [self sd_addActivityIndicator];
    }
    //有URL就去加载图片
    __weak __typeof(self)wself = self;
    id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        __strong __typeof (wself) sself = wself;
        [sself sd_removeActivityIndicator];
        //如果对象被释放掉了就return
        if (!sself) {
            return;
        }
        dispatch_main_async_safe(^{
            //切换到主线程时,如果对象被释放了就return
            if (!sself) {
                return;
            }
            //如果加载到图片了,并且用户设置了不让自动给UI赋值,那就返回给你让你自己看着办
            if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                completedBlock(image, error, cacheType, url);
                return;
            //如果加载到了图片,就给UI赋值
            } else if (image) {
                [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                [sself sd_setNeedsLayout];
            //如果没加载到,就用placeholder赋值
            } else {
                if ((options & SDWebImageDelayPlaceholder)) {
                    [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                }
            }
            //顺便返回给你
            if (completedBlock && finished) {
                completedBlock(image, error, cacheType, url);
            }
        });
    }];
    [self sd_setImageLoadOperation:operation forKey:validOperationKey];
//如果url是nil,就可以给你抛错啦~
} else {
    dispatch_main_async_safe(^{
        [self sd_removeActivityIndicator];
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            completedBlock(nil, error, SDImageCacheTypeNone, url);
        }
    });
}
}

3、知识点简析
1)为什么要判断以下代码,为什么判断了两次?

 if (!sself) {
        return;
    }

假如对象是UITableViewCell上的一个UIImageView,就会随着页面滑动Cell消失的时候被释放掉。而此时有可能图片刚加载出来。判断是为了防止对象释放所带来的异常。而之后又判断了一次,是防止在切换线程的过程中对象被释放的情况。毕竟切换线程也是需要时间的。

2) dispatch_main_async_safe(block)。从上述代码我们可以看出,所有更新UI的操作都是在dispatch_main_async_safe()中完成的。那为什么我们不直接切换到主线程去刷新UI呢?

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
    block();\
} else {\
    dispatch_async(dispatch_get_main_queue(), block);\
}
#endif

点进去发现,这个方法的含义是先比较当前线程是否为主线程,是则直接执行block(这时候就不用切换线程了,因为切换线程也是要耗费时间的),不是再切换到主线程执行block。

3)为什么用了__weak之后又用了__strong呢?

    __weak __typeof(self)wself = self;
    id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
        __strong __typeof (wself) sself = wself;

我们都知道,用__weak是防止循环引用。而之后又用了__strong,是避免weak修饰的变量随机被释放而导致异常。那block 里 strong self 后,block 不是也会持有 self 吗?而 self 又持有 block ,那不是又循环引用了? 当然不会咯,因为此时的sself是一个新的引用(变量/对象)了,已经不是self了。

二、 loadImageWithURL--图片加载器

在URL正确的情况下,先从缓存中找,如果没找到就去下载。 并将下载好或从缓存中查找到的图片返回
1、程序主流程


image.png

2、源码分析

- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                 options:(SDWebImageOptions)options
                                progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                               completed:(nullable SDInternalCompletionBlock)completedBlock {
//如果有completedBlock  就在控制台打印如下信息
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

//很多人都会把URL当成字符串来传,那如果真接收到字符串了,我们就转换一下~
if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}

//有时候也会发送NSNull这样的情况,为了更严谨,我们再判断一下
if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}

//初始化一个可以控制加载图片方法的一个操作,这个操作里面既能控制缓存,又能控制下载。而且在方法结束的时候返回给外界
//__block,即在block内可修改。
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

//判断这个URL是否是个错误的URL
BOOL isFailedUrl = NO;
if (url) {
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
}

//如果URL不合格就直接返回了,也没有必要去缓存里查找啦
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;
}

//如果合格,加到runningOperations里面
@synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL:url];

//URL合格,先去缓存中找,并返回查询结果。 此缓存为二级缓存,即先去内存中找,如果找不到,去磁盘里找,然后再返回结果。
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
    if (operation.isCancelled) {
        [self safelyRemoveOperationFromRunning:operation];
        return;
    }
    //此后根据查询结果来判断是否需要下载。以下是需要下载的条件
    //  如果缓存中没找到图片
    //||用户设置了更新缓存 && 用户没有响应imageManager:shouldDownloadImageForURL:(没有响应就不会出现返回NO的情况,就可以下载了)
    //||用户执行了imageManager:shouldDownloadImageForURL:,并返回yes(既这个网址需要被下载)
    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];
        }

        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) {
            // 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;
        }
        //执行下载任务
        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];

                if (   error.code != NSURLErrorNotConnectedToInternet
                    && error.code != NSURLErrorCancelled
                    && error.code != NSURLErrorTimedOut
                    && error.code != NSURLErrorInternationalRoamingOff
                    && error.code != NSURLErrorDataNotAllowed
                    && error.code != NSURLErrorCannotFindHost
                    && error.code != NSURLErrorCannotConnectToHost
                    && error.code != NSURLErrorNetworkConnectionLost) {
                    @synchronized (self.failedURLs) {
                        [self.failedURLs addObject:url];
                    }
                }
            }
            //如果下载正确,就可以在内存中和磁盘中存储图片了
            else {
                if ((options & SDWebImageRetryFailed)) {
                    @synchronized (self.failedURLs) {
                        [self.failedURLs removeObject:url];
                    }
                }
                BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                //如果没有downloadedImage,也不用存储了,而且options & SDWebImageRefreshCached && cachedImage  这种情况下已经callback过了,也不用进行callCompletion操作,所以什么都不用做
                if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                
                } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                    //以下这种情况会存储被用户转换过的图片
                    //   如果有downloadedImage
                    // &&(下载的图片不是GIF || 用户设置了即使是GIF也可以transform)
                    // &&用户要对图片做一些转换(即响应了这个代理方法“(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];
                            // 如果图片被转换过,imageData就存储一个nil
                            [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 {
                    // 存储正常图片。判断一下finished是因为 如果用户设置了options & SDWebImageProgressiveDownload,那在下载过程中每接收到一点数据didReceiveData,就会执行callCompletion,以便用户实现渐进效果,但此时图片并没有下载完成,所以不需要存储。
                    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];
            }
        }];
        //给operation的cancelBlock赋值,这个block主要用于终止下载操作。当外界调用operation的cancell方法时,这个block会被执行,
        //同时这个operation里面的cacheOperation也会被cancell。 所以它既能控制下载也能控制缓存,返回给外界以方便使用。
        operation.cancelBlock = ^{
            [self.imageDownloader cancel:subOperationToken];
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self safelyRemoveOperationFromRunning:strongOperation];
        };
    //如果从缓存中查询到图片了,就callCompletion查询到的图片
    } 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];
    //如果缓存中没有查询到图片,并且用户不允许下载,即执行过[self.delegate imageManager:self shouldDownloadImageForURL:url]这个方法后返回NO。
    } 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;
}

3、知识点解析
1)断言(NSAssert)

#define NSAssert(condition,desc)

如果condition不成立,则程序崩溃。并在控制台打印desc。

NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

如源码中的代码,如果没有completedBlock,程序就会崩溃,并会在控制台打印If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead。从源代码中我们发现,确实在往后的代码中没有判断是否有completedBlock,都是直接调用completedBlock的。

2)@synchronized (self.runningOperations) {...}

@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}

这是一个互斥锁,防止多条线程同一时间对self.runningOperations进行修改。也就是说,当一个线程抢占了这个任务时,这个锁就会排斥其他线程,起到了线程保护的作用。保证了self.runningOperations的准确性。

三、queryCacheOperationForKey --存储器

1、程序流程


image.png

2、源码分析

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//如果key是nil,就直接doneBlock,返回
if (!key) {
    if (doneBlock) {
        doneBlock(nil, nil, SDImageCacheTypeNone);
    }
    return nil;
}

//否则,先从内存中找
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
    // 如果找到了,就可以doneBlock,返回了
    NSData *diskData = nil;
    if ([image isGIF]) {
        diskData = [self diskImageDataBySearchingAllPathsForKey:key];
    }
    if (doneBlock) {
        doneBlock(image, diskData, SDImageCacheTypeMemory);
    }
    return nil;
}

//如果没有找到,就去磁盘中找
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
    if (operation.isCancelled) {
        // do not call the completion if cancelled
        return;
    }

    @autoreleasepool {
        NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        UIImage *diskImage = [self diskImageForKey:key];
        //如果应该在内存中存储的话就在内存存储一份
        if (diskImage && self.config.shouldCacheImagesInMemory) {
            NSUInteger cost = SDCacheCostForImage(diskImage);
            [self.memCache setObject:diskImage forKey:key cost:cost];
        }

        if (doneBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
            });
        }
    }
});
return operation;
}

3、知识点简析
1)NSCache
大家可以找一下相关资料,在这里就不做多的分析了,(__) ……
2)磁盘里面的沙盒

image.png

当我们使用HSHomeDirectory()就可以访问到我们APP所对应的沙盒了。在沙盒里,系统默认为我们创建三个文件夹,分别是Documents,Library和tmp。当然我们也可以在沙盒路径下自己新建文件夹。
Documents主要放一些不占空间,又比较重要的文件。如果应用需要备份,系统也是备份Documents下的文件。
Library主要放一些大文件。又多又占地方。比如微信的聊天记录就是存放在这个文件夹下面的,SDWebImage也是把图片存放在这个文件夹下面的。
tmp主要放一些临时文件。
我们把Library文件夹打开
image.png

偏好设置[NSUserDefaults standardUserDefaults]就是存放在Library目录下的,同时系统还自动为我们生成了一个文件夹叫Caches,也可以在这个目录下自己生成目录。
3)为什么要使用自动释放池?
这里添加的自动释放池与以下代码有异曲同工之处

for (int i = 0; i < 10000; i++) {
    @autoreleasepool {
        NSString *str = [NSString stringWithFormat:@"zyy"];
    }
 }

去磁盘中查询图片也是也是重复执行的任务。为了防止对象不断增多,每查询一次就将所用到的对象释放,可以降低内存峰值,防止内存持续增加,再突然降低。

四、downloadImageWithURL--下载器

1、程序主流程


image.png

2、源码分析

 - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                               options:(SDWebImageDownloaderOptions)options
                                              progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                             completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;

     // 这个方法主要调用了addProgressCallback这个方法,既创建一个下载任务,返回这个任务的标识

     return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback: 
     {我是初始化下载任务,并将这个任务返回的block}];
}

以下是addProgressCallback方法源码

- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                               forURL:(nullable NSURL *)url
                                       createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// 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;
}

__block SDWebImageDownloadToken *token = nil;

dispatch_barrier_sync(self.barrierQueue, ^{
    //先看看与这个URL对应的operation是否存在,防止对同一个网址下载多次
    SDWebImageDownloaderOperation *operation = self.URLOperations[url];
    if (!operation) {
   //当operation 不存在的时候才执行这个block,即创建下载任务,并对其进行初始化
   //注!!!:这个时候才执行从downloadImageWithURL方法中传过来的那个代码块儿~!这个时候可以跳到下一块儿源码,看看是如何创建并初始化的
        operation = createCallback();
        self.URLOperations[url] = operation;

        __weak SDWebImageDownloaderOperation *woperation = operation;
        //当这个下载任务完成的时候,从URLOperations中移除
        operation.completionBlock = ^{
            dispatch_barrier_sync(self.barrierQueue, ^{
                SDWebImageDownloaderOperation *soperation = woperation;
                if (!soperation) return;
                if (self.URLOperations[url] == soperation) {
                    [self.URLOperations removeObjectForKey:url];
                };
            });
        };
    }
    id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

    //为这个下载任务配一个标识,方便外界随时cancell
    token = [SDWebImageDownloadToken new];
    token.url = url;
    token.downloadOperationCancelToken = downloadOperationCancelToken;
});

return token;
}

那个创建下载任务的代码块儿

^SDWebImageDownloaderOperation *{
    //这个block主要初始化了一个下载任务,并将它返回
    __strong __typeof (wself) sself = wself;
    NSTimeInterval timeoutInterval = sself.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 (sself.headersFilter) {
        request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
    }
    else {
        request.allHTTPHeaderFields = sself.HTTPHeaders;
    }
    
    //自定义了自己的operation,专程用来下载的。  显然这个时候系统的NSBlockOperation 和 NSInvocationOperation 已经满足不了需求了,需要自定义了
    //以下都是给这个下载操作初始化的代码,就不细说咯
    SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
    operation.shouldDecompressImages = sself.shouldDecompressImages;
    
    if (sself.urlCredential) {
        operation.credential = sself.urlCredential;
    } else if (sself.username && sself.password) {
        operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
    }
    
    if (options & SDWebImageDownloaderHighPriority) {
        operation.queuePriority = NSOperationQueuePriorityHigh;
    } else if (options & SDWebImageDownloaderLowPriority) {
        operation.queuePriority = NSOperationQueuePriorityLow;
    }
    //初始化以后并将这个操作添加到队列里面
    [sself.downloadQueue addOperation:operation];
    //设置执行顺序。如果是后进先出,那就把线程添加到最先执行那个线程的后面
    if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
        [sself.lastAddedOperation addDependency:operation];
        sself.lastAddedOperation = operation;
    }

    return operation;

3、知识点简析
1)下载任务SDWebImageDownloaderOperation类是怎么下载的?
具体代码就不分析了,来简单的浅析一下(偷个懒O(∩_∩)O~)。它是对NSOperation的一个封装(想学习自定义NSOperation的童鞋,SDWebImage真的是个很好的例子)。 简单来讲,就是我们把网络下载的URL啊,网络证书啊,options啊....还有它需要执行的progressBlock和completeBlock给它,然后它去请求网络,把请求回来的数据先解读成一张图片,并通过回调progressBlock和completeBlock把图片返还给我们。
2)dispatch_barrier_syncdispatch_barrier_async

     dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

这个方法重点是你传入的 queue,当你传入的 queue 是通过 DISPATCH_QUEUE_CONCURRENT参数自己创建的 queue 时,这个方法会阻塞这个 queue(注意是阻塞 queue ,而不是阻塞当前线程),一直等到这个 queue 中排在它前面的任务都执行完成后才会开始执行自己,自己执行完毕后,再会取消阻塞,使这个 queue 中排在它后面的任务继续执行。
如果你传入的是其他的 queue, 那么它就和dispatch_async 一样了。

  dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

这个方法的使用和上一个一样,传入 自定义的并发队列DISPATCH_QUEUE_CONCURRENT,它和上一个方法一样的阻塞 queue,不同的是 这个方法还会 阻塞当前线程。
如果你传入的是其他的 queue, 那么它就和 dispatch_sync一样了。

总结:当我们使用SDWebImage给UI赋值时,它会先从内存中读取图片,读取不到再从磁盘中读取,都没有读取成功就去下载图片,下载成功存入缓存,并给UI赋值。其中下载器、缓存器、加载器和UI赋值器分工明确,条理清晰。 牵扯的知识面也很广,有下载类NSURLSession、缓存类NSCache、对磁盘的操作NSFileManager,以及多线程 dispatch和NSOperation等。也是学习这些模块儿的一个很好的例子呀!
好啦。以上是我对SDWebImage源码流程上的分析,有不对的地方也尽管指出来~~我们共同学习,嘿嘿

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,444评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,867评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,157评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,312评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,673评论 3 289
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,802评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,470评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,696评论 2 250
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,187评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,538评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,188评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,127评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,889评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,741评论 2 274

推荐阅读更多精彩内容