iOS进阶高级iOS Developer对移动开发有帮助

防面试-SD_WebImage

2018-04-27  本文已影响129人  强子ly

目录

记得当年对SDWebImage这个库,会用、然后把从内存到磁盘缓存过程说一下就能轻松征服一个面试官,现在爱上了读源码,把大神们的思路及想法整理一下,很有收获。
篇幅有限,本文只从流程的角度解读一下源码

一、SDWebImage优点

如果你不读源码,很少有人会去关心SD里面还有这么多很优的操作,例如:

SDWebImage优点.png
以前写过一个关于动态绑定的文章,浅谈runtime关联,初学者可以看一下。

二、位运算枚举(NS_OPTIONS)

SDWebImage库里面有大量的NS_OPTIONS枚举,判断的时候关于NS_OPTIONS的二进制运算也用到了很多,所以如果不了解这些的话,有些逻辑还是不甚明了的。

在这里做一下简单介绍

typedef NS_OPTIONS(NSUInteger, OptionType) {
    OptionType1 = 0,       // 二进制 0000, 十进制 0
    OptionType2 = 1 << 0,  //       0001,       1
    OptionType3 = 1 << 1,  //       0010,       2
    OptionType4 = 1 << 2   //       0100,       4
};
    /*
     *  << >> | & 这些运算相信在大学C语言都学过,这里不做过多介绍
     *
     *  1、将两个NS_OPTIONS枚举进行 “|” 运算,得到的结果肯定包含这两个枚举类型(option)
     *  2、将运算得到的枚举(option)与被比较枚举(OptionType3)值做 “&” 运算,即可得到option是否包含(OptionType3)
     *
     *  原理:
     
        option = OptionType1 | OptionType2      0001
                                                0010
                                             =  0011
     *  这样无论用OptionType1还是OptionType2再和option进行 "|" 运算,
                 得到的结果都是非零,可用于 option 是否包含着两个枚举值的判断
     */

    OptionType option = OptionType1 | OptionType2;
    if (option & OptionType3)
    {
        NSLog(@"option 包含 OptionType3");
    }else
    {
        NSLog(@"option 不包含 OptionType3");
    }

    NSLog(@"============================");

    // 增加枚举选项
    option = option | OptionType4;
    if (option & OptionType4)
    {
        NSLog(@"option 包含 OptionType4");
    }else
    {
        NSLog(@"option 不包含 OptionType4");
    }

    NSLog(@"============================");

    // 减少枚举选项
    option = option & (~OptionType4);
    if (option & OptionType4)
    {
        NSLog(@"option 包含 OptionType4");
    }else
    {
        NSLog(@"option 不包含 OptionType4");
    }
2018-04-27 10:31:04.946240+0800 位运算枚举[1938:69662] option 不包含 OptionType3
2018-04-27 10:31:04.946394+0800 位运算枚举[1938:69662] ============================
2018-04-27 10:31:04.946506+0800 位运算枚举[1938:69662] option 包含 OptionType4
2018-04-27 10:31:04.946602+0800 位运算枚举[1938:69662] ============================
2018-04-27 10:31:04.946709+0800 位运算枚举[1938:69662] option 不包含 OptionType4

三、基本原理

1、显示placeholderImage
2、SDImageCache从缓存中查找图片是否已经下载
3、先从内存图片缓存查找是否有图片
4、如果内存中有图片缓存,显示图片
5、如果内存中没有,生成NSInvocationOperation添加到执行队列开始从硬盘查找图片缓存
6、如果硬盘中有,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存),显示图片
7、如果硬盘中没有,说明该图片没有缓存,需要下载图片,共享或重新生成一个下载器SDWeb-
-ImageDownLoader开始下载图片
8、开始图片网络请求,下载数据
9、数据下载完成后交给SDWebImageDecoder做图片解码
10、回调展示图片
11、图片保存到硬盘缓存和内存缓存
12、SDImageCache初始化会注册一些通知,在内存警告或退到后台的时候清理内存图片缓存,
-应用结束的时候清理过期图片

入口:给UIIImageView设置默认图片.png

3.1、常用入口

/** 常用加载方法 */
[self.giftImageView sd_setImageWithURL:[NSURL URLWithString:model.roomPic]
                      placeholderImage:[UIImage imageNamed:@"public_image_placeholder"]];

/** 在加载图片过程中可获取图片的下载进度和加载成功与否  */
[self.giftImageView sd_setImageWithURL:[NSURL URLWithString:model.roomPic]
                      placeholderImage:[UIImage imageNamed:@"public_image_placeholder"]
                             completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
    
}];

/** 下载图片 */
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager.imageDownloader downloadImageWithURL:[NSURL URLWithString:model.img]
                                      options:SDWebImageDownloaderHighPriority
                                     progress:nil
                                    completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
    
}];

3.2、取消下载操作

- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key
{
    /*
     *  从队列中取消正在进行的下载程序
     *
     *  获取添加在UIView的自定义属性
     */
    SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
    id<SDWebImageOperation> operation;
    @synchronized (self) {
        operation = [operationDictionary objectForKey:key];
    }
    if (operation) {
        // 实现SDWebImageOperation协议
        if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]){
            [operation cancel];
        }
        @synchronized (self) {
            [operationDictionary removeObjectForKey:key];
        }
    }
}

3.3、核心方法

/**
 下载核心方法

 @param url 图片url
 @param options 下载方式
 @param progressBlock 加载进度block
 @param completedBlock 加载结果block
 @return SDWebImageOperation
 */
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
    
}
/*
 *  容错处理
 *
 *  1、如果不传入加载结果回调block,则报错(⚠️:下载为什么不回调结果)
 *  2、如果传入的是NSString类型,则转换为url类型
 *  3、如果传入的不是url不是NSURL类型则将url置空
 */
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
if ([url isKindOfClass:NSString.class]) {
    url = [NSURL URLWithString:(NSString *)url];
}

if (![url isKindOfClass:NSURL.class]) {
    url = nil;
}
/*
 *  创建SDWebImageCombinedOperation
 */
SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;

/*
 *  判断当前url是否在错误集合中
 *
 *  self.failedURLs, 错误URL集合,如果当前url下载出错,则将其添加到次错误集合中,下次不再进行操作
 
 *  @synchronized, 创建互斥锁,保证线程安全
 
 */
BOOL isFailedUrl = NO;
if (url)
{
    @synchronized (self.failedURLs)
    {
        isFailedUrl = [self.failedURLs containsObject:url];
    }
}

/*
 *  错误回调条件
 *  1、如果url为空
 *  2、检测options中是否包含SDWebImageRetryFailed(详情见:二、位运算枚举)
 *  3、url在failedURLs错误数组中
 */
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;
}

// 将operation添加到任务数组中,(此操作需加@synchronized保护线程安全)
@synchronized (self.runningOperations) {
    [self.runningOperations addObject:operation];
}
NSString *key = [self cacheKeyForURL: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;
        }
        
        /*
         *  Check whether we should download image from network
         *  检查是否应该从网络下载图像
         *
         *  1、缓存中没有找到image图片 (SDWebImageFromCacheOnly)
         *  2、图片需要刷新 (SDWebImageRefreshCached)
         *  3、下载代理存在并且执行
         */
        BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly))
        && (!cachedImage || options & SDWebImageRefreshCached)
        && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
        
        if (shouldDownload) {
            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.
                 *
                 *  如果该图像在缓存中,则回调这个需要传递的image,并且通知让NSURLCache重新进行缓存
                 */
                [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
             *
             *  如果没有请求图片或者请求刷新,则下载该图片并且调用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;
            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
                 *
                 *  如果图像缓存但强制刷新,则忽略NSURLCache中读取的图像
                 */
                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
            }
            
            
            // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
            
            
            /*
             *  创建下载操作
             *
             *
             */
            __weak typeof(strongOperation) weakSubOperation = strongOperation;
            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;
                    
                    /*
                     *  Check whether we should block failed url
                     *
                     *  检测url下载是否失败
                     */
                    //
                    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);
                    }
                    
                    /*
                     *  将失败的url添加到失败url数组中
                     */
                    if (shouldBlockFailedURL)
                    {
                        @synchronized (self.failedURLs)
                        {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    /*
                     *  如果设置了下载失败重试,则将url从失败的url数组中移除
                     */
                    if ((options & SDWebImageRetryFailed))
                    {
                        @synchronized (self.failedURLs)
                        {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    /*
                     *  如果在缓存中找到了SDWebImageCacheMemoryOnly
                     */
                    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
                         *
                         *  图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。
                         */
                    } 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), ^{
                            
                            /*
                             *  调用代理方法
                             *  获取transform以后的图片
                             */
                            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);
                                }
                                
                                /*
                                 *  缓存transform之后的图片
                                 */
                                [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), ^{
                                    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];
                            }
                        }
                        [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
        {
            /*
             *  Image not in cache and download disallowed by delegate
             *  主线程执行完成回调
             */
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            /*
             *  从正在执行的操作列表中安全移除组合操作
             */
            [self safelyRemoveOperationFromRunning:strongOperation];
        }
    }];


生成下载器下载

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

        /*
         *  In order to prevent from potential duplicate caching (NSURLCache + SDImageCache)
         we disable the cache for image requests if told otherwise
         *
         *  为了避免 NSURLCache 和 SDImageCache 同时缓存,默认不允许image对象的NAURLCache对象         
         */
        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        
        
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
        
        /*
         *  设置cookies缓存
         */
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        
        /*
         *  默认情况请求和相应是顺序的 请求--->相应
         *
         *  HTTPShouldUsePipelining 属性值为yes,则允许不必等到respone就可以再次请求,可以大大提高网络请求的效率
         */
        request.HTTPShouldUsePipelining = YES;
        
        /*
         *  设置请求头
         */
        if (sself.headersFilter)
        {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
        }else
        {
            request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
        }
        
        /*
         *  SDWebImageDownloaderOperation  继承  NSOperation
         *
         *  shouldDecompressImages  是否压缩返回的图片
         */
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        /*
         *  指定验证信息
         *
         *  urlCredential : SSL验证
         *  Basic验证
         */
        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;
        }
        
        // 设置下载的顺序
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder)
        {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }
        return operation;
    }];
}

缓存查找核心方法

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock
{
    // 1、检测key
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
    
    // 2、从内存中查找,如果找到image,直接doneBlock回调
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
    if (shouldQueryMemoryOnly) {
        if (doneBlock) {
            doneBlock(image, nil, SDImageCacheTypeMemory);
        }
        return nil;
    }
    
    NSOperation *operation = [NSOperation new];
    void(^queryDiskBlock)(void) =  ^{
        
        // 如果该线程已经取消操作,直接return
        if (operation.isCancelled){
            return;
        }
        
        // 使用内存池及时释放资源
        @autoreleasepool {
            
            //3、如果内存中不存在该图片,则在磁盘中查找
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage;
            SDImageCacheType cacheType = SDImageCacheTypeDisk;
            
            // 4、如果在内存中已存在
            if (image)
            {
                diskImage = image;
                cacheType = SDImageCacheTypeMemory;
            } else if (diskData)
            {
                /*
                 *  decode image data only if in-memory cache missed
                 *  如果图片在磁盘中找到则,将其缓存到内存中
                 */
                diskImage = [self diskImageForKey:key data:diskData];
                if (diskImage && self.config.shouldCacheImagesInMemory)
                {
                    NSUInteger cost = SDCacheCostForImage(diskImage);
                    [self.memCache setObject:diskImage forKey:key cost:cost];
                }
            }
            
            // 回调用block结果
            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 {
        // 在ioQueue中串行处理所有磁盘缓存
        dispatch_async(self.ioQueue, queryDiskBlock);
    }
    return operation;
}

参考文章:
一行行看SDWebImage源码
搬好小板凳看SDWebImage源码解析

上一篇下一篇

猜你喜欢

热点阅读