• SDWebImage源码探究(三)


    详解:

    • 入口
    UIImageView+WebCache.m
    //这个是入口,下载图片的结果方法最终都是走的这个方法
    
    - (void)sd_setImageWithURL:(nullable NSURL *)url
              placeholderImage:(nullable UIImage *)placeholder
                       options:(SDWebImageOptions)options
                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                     completed:(nullable SDExternalCompletionBlock)completedBlock {
        [self sd_internalSetImageWithURL:url
                        placeholderImage:placeholder
                                 options:options
                            operationKey:nil
                           setImageBlock:nil
                                progress:progressBlock
                               completed:completedBlock];
    }
    • UIView的分类
    UIView+WebCache.m
    - (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);
        
        if (!(options & SDWebImageDelayPlaceholder)) {
            dispatch_main_async_safe(^{
                [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
            });
        }
        
        //如果URL不为空,调用管理器SDWebImageManager
        if (url) {
            //加载指示器
            if ([self sd_showActivityIndicatorView]) {
                [self sd_addActivityIndicator];
            }
            
            __weak __typeof(self)wself = self;
            //调用管理器SDWebImageManager
            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];
                if (!sself) {
                    return;
                }
                dispatch_main_async_safe(^{
                    if (!sself) {
                        return;
                    }
                    if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                        completedBlock(image, error, cacheType, url);
                        return;
                    }
                    else if (image) {
                        [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                    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为空就抛出异常
        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);
                }
            });
        }
    }
    • 调用管理类 SDWebImageManager
    SDWebImageManager.m
    - (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传递的NSString而不是NSURL,框架也会给我们自动转化
        if ([url isKindOfClass:NSString.class]) {
            url = [NSURL URLWithString:(NSString *)url];
        }
    
        //防崩溃处理,如果不是NSString和NSURL类型,就赋值为nil,防止app崩溃
        if (![url isKindOfClass:NSURL.class]) {
            url = nil;
        }
    
        __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
        __weak SDWebImageCombinedOperation *weakOperation = operation;
    
        BOOL isFailedUrl = NO;
        if (url) {
            //这里的是failedURLs是NSMutableSet集合
            @synchronized (self.failedURLs) {
                //判断集合中是否包含这个url
                isFailedUrl = [self.failedURLs containsObject: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;
        }
    
        @synchronized (self.runningOperations) {
            //如果操作operation不为空,就讲该操作放在可变数组runningOperations里面
            [self.runningOperations addObject:operation];
        }
        //获取urlKey作为操作的key
        NSString *key = [self cacheKeyForURL:url];
    
        //查询该操作是否在缓存操作中
        operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
            //如果缓存中有这个操作,就移除这个operation
            if (operation.isCancelled) {
                [self safelyRemoveOperationFromRunning:operation];
                return;
            }
            
            //如果缓存中没有图片,就调用先判断是否可以下载
            if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
                if (cachedImage && options & SDWebImageRefreshCached) {
                    //如果缓存中有图片,设置了SDWebImageRefreshCached,就会重新下载,NSURLCache从服务器重新下载
                    [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) {
                    downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
                    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) {
                        //如果操作不存在或者操作被取消,就什么都不做,如果我们调用完成block,对于同一个对象,在这个block和其他的完成block之间就会产生竞争状态,因此如果这个被二次调用,我们就会覆盖新数据。
                    }
                    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) {
                            @synchronized (self.failedURLs) {
                                [self.failedURLs addObject:url];
                            }
                        }
                    }
                    else {
                        //如果下载失败的话,就将这个url从集合set中移除
                        if ((options & SDWebImageRetryFailed)) {
                            @synchronized (self.failedURLs) {
                                [self.failedURLs removeObject:url];
                            }
                        }
                        
                        BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
    
                        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), ^{
                                UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
    
                                if (transformedImage && finished) {
                                    BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
                                    // pass nil if the image was transformed, so we can recalculate the data from the image
                                    [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 {
                            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 = ^{
                    [self.imageDownloader cancel:subOperationToken];
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    [self safelyRemoveOperationFromRunning:strongOperation];
                };
            }
            //如果有缓存图片
            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];
            }
            else {
                //缓存中没有图片,也不用代理下载,并移除操作
                __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;
    }
    • 下载前,先检查缓存 SSImageCache
    SDImageCache.m
    //查询是否缓存过该操作
    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock
    {
        //不存在这个key,就调用完成block
        if (!key) {
            if (doneBlock) {
                doneBlock(nil, nil, SDImageCacheTypeNone);
            }
            return nil;
        }
    
        // 先检查内存是否存在该图片
        UIImage *image = [self imageFromMemoryCacheForKey:key];
        if (image) {
            NSData *diskData = nil;
            if ([image isGIF]) {//gif
                diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            }
            if (doneBlock) {
                doneBlock(image, diskData, SDImageCacheTypeMemory);
            }
            return nil;
        }
    
        //实例化一个新操作
        NSOperation *operation = [NSOperation new];
        dispatch_async(self.ioQueue, ^{
            if (operation.isCancelled) {
                //取消操作就返回
                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(), ^{
                        //完成的回调,返回图片,数据以及图片的获取途径
    //                    typedef NS_ENUM(NSInteger, SDImageCacheType) {
    //                       //缓存中不存在图片,要从网上下载
    //                        SDImageCacheTypeNone,
    //                       //图片缓存在磁盘
    //                        SDImageCacheTypeDisk,
    //                         图片缓存在内存
    //                        SDImageCacheTypeMemory
    //                    };
    
                        doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                    });
                }
            }
        });
    
        return operation;
    }
    • 如果缓存中没有,就下载图片 SDWebImageDownloader
    SDWebImageDownloader.m
    //下面就是下载图片的方法了
    
    - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                       options:(SDWebImageDownloaderOptions)options
                                                      progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                     completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
        __weak SDWebImageDownloader *wself = self;
    
        //进度回调
        return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
            __strong __typeof (wself) sself = wself;
            //设置超时15s
            NSTimeInterval timeoutInterval = sself.downloadTimeout;
            if (timeoutInterval == 0.0) {
                timeoutInterval = 15.0;
            }
    
            // NSURLCache + SDImageCache)防止重复缓存,开始请求图片
            NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
            request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
            request.HTTPShouldUsePipelining = YES;
            //过滤HTTP请求,该字典用来HTTP请求的header
            if (sself.headersFilter) {
                request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
            }
            else {
                request.allHTTPHeaderFields = sself.HTTPHeaders;
            }
            SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
            operation.shouldDecompressImages = sself.shouldDecompressImages;
            
            //设置默认的 URL credential
            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;
            }
            
            //将操作放在NSOperationQueue中
            [sself.downloadQueue addOperation:operation];
            //设置操作的执行顺序
            //typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
                //队列模式  先进先出,这个是默认值
                //SDWebImageDownloaderFIFOExecutionOrder,
                //堆栈模式,后进先出
                //SDWebImageDownloaderLIFOExecutionOrder
            //};
    
            if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
                //添加操作的依赖关系,这里利用lastAddedOperation记录操作
                [sself.lastAddedOperation addDependency:operation];
                sself.lastAddedOperation = operation;
            }
    
            return operation;
        }];
    }
    • 上面的SDWebImageDownloadToken 是从下面这个方法获取的
    SDWebImageDownloader.m
    //加入进度回调,返回SDWebImageDownloadToken对象
    
    - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                               completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                       forURL:(nullable NSURL *)url
                                               createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
        //如果URL用于回调字典的key,不能为空,如果为空,就会立即调用完成block,不带有图片和数据
        if (url == nil) {
            if (completedBlock != nil) {
                completedBlock(nil, nil, nil, NO);
            }
            return nil;
        }
    
        __block SDWebImageDownloadToken *token = nil;
    
        //加入GCD阻塞
        dispatch_barrier_sync(self.barrierQueue, ^{
            //根据url作为key返回操作
            SDWebImageDownloaderOperation *operation = self.URLOperations[url];
            if (!operation) {
                operation = createCallback();
                self.URLOperations[url] = operation;
    
                __weak SDWebImageDownloaderOperation *woperation = operation;
                operation.completionBlock = ^{
                  SDWebImageDownloaderOperation *soperation = woperation;
                  if (!soperation)
                      return;
                 //如果这次加入的操作已经存在,就移除该操作
                  if (self.URLOperations[url] == soperation) {
                      [self.URLOperations removeObjectForKey:url];
                  };
                };
            }
            id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
            //重新初始化SDWebImageDownloadToken对象
            token = [SDWebImageDownloadToken new];
            token.url = url;
            token.downloadOperationCancelToken = downloadOperationCancelToken;
        });
    
        return token;
    }
    • 下载完成后就对图片解码
    #import <Foundation/Foundation.h>
    #import "SDWebImageCompat.h"
    
    @interface UIImage (ForceDecode)
    
    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image;
    
    + (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image;
    
    @end
  • 相关阅读:
    命令行语法格式及特殊字符
    自己实现strncasecmp
    [windows bat]如何启动一个新的cmd窗口并在其内执行命令
    关于怎么提取m3u8地址
    如何获取各大平台的播放地址(获得优酷的m3u8播放地址)为例
    Python爬取视频指南
    win10更改pip源
    python获取文件路径
    sublime 经验总结 主题有 less2css
    35 个必须有的Bootstrap工具和生成器
  • 原文地址:https://www.cnblogs.com/qiyiyifan/p/10318127.html
Copyright © 2020-2023  润新知