SDWebImage 部分源码解读

前言

SDWebImage作为iOS开发者的流行框架,功能十分强大,常用的使用场景如 UIImageView 加载图片、图片下载、获取图片下载进度以及下载状态,缓存图片(默认同时缓存到memory和disk)等等。今天主要分享的是下面这个每个iOS攻城狮都用过的SDWebImage的函数的底层设计。

- (void)sd_setImageWithURL:(NSURL *)url  placeholderImage:(UIImage *)placeholder;

源码解读

UIImageView+WebCache 提供了多个加载图片的方法,但是最后所有的方法都会调用下面这个方法。

- (void)sd_setImageWithURL:(NSURL *)url 
                       placeholderImage:(UIImage *)placeholder
                       options:(SDWebImageOptions)options 
                       progress:(SDWebImageDownloaderProgressBlock)progressBlock 
                       completed:(SDWebImageCompletionBlock)completedBlock;

进去看下,两段代码翻译出来就是

  1. 在加载图片第一步,会先把这个 UIImageView 动态添加的下载操作取消
  2. 如果之前这个 UIImageView 没有添加过这个属性则不进行这个操作,这么做的原因是: SDWebImage 将图片对应的下载操作放到 UIView 的一个自定义字典属性 operationDictionary 中,取消下载操作第一步也是从这个 UIView 的自定义字典属性 operationDictionary 中取出所有的下载操作,然后依次调用取消方法,最后将取消的操作从 operationDictionary 字典属性中移除
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    // 取消当前图像下载
    [self sd_cancelCurrentImageLoad];
    // 关联对象
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            // 设置占位图像
            self.image = placeholder;
        });
    }
    
    if (url) {
        __weak UIImageView *wself = self;
        // 实例化 SDWebImageOperation 操作
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                // 如果得到图像
                if (image) {
                    // 记录图像
                    wself.image = image;
                    // 重绘视图
                    [wself setNeedsLayout];
                } else {
                    // 如果没有得到图像
                    // 如果设置了 SDWebImageDelayPlaceholder 选项
                    if ((options & SDWebImageDelayPlaceholder)) {
                        // 设置占位图像
                        wself.image = placeholder;
                        // 重绘视图
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        // 移除之前没用的图片下载操作之后就创建一个新的图片下载操作,操作完成后讲操作设置到 UIView 的自定义字典属性 operationDictionary 中
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        dispatch_main_async_safe(^{
            NSError *error = [NSError errorWithDomain:@"SDWebImageErrorDomain" code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}
// 取消图片加载操作
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // Cancel in progress downloader from queue
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    id operations = [operationDictionary objectForKey:key];
    if (operations) {  
        if ([operations isKindOfClass:[NSArray class]]) {
        // SDWebImageOperatio数组,将数组中的每个加载操作都取消
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
        // 调用SDWebImageOperation 的protocol方法
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];  //取消后 移除这个属性
    }
}

先判断了url的有效性, SDWebImage 保存有黑名单failedURLs,判断失败后不会重复请求

 // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) { //url格式有问题致nil
        url = nil;
    }

    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; //new下载操作
    __weak SDWebImageCombinedOperation *weakOperation = operation;

    BOOL isFailedUrl = NO;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url]; //判断url是否是失效过的url
        }
    }
    //url长度为0 或 u设置了失败不再请求请求且url为失效url,返回error
    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;
    }

url没问题,则开始下载任务, 在下载之前,首先会根据图片的URL生成唯一的key,用来查找内存的磁盘中的缓存

 NSString *key = [self cacheKeyForURL:url]; //根据url生成唯一的key
    //缓存
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {

查找缓存的具体代码, 首先会先根据key请求内存缓存, 若存在则block传回缓存图片, 若没有则开辟子线程(异步不阻塞主线程)查找磁盘中缓存, 若在磁盘中查找到,会将缓存数据在内存中也缓存份,然后block传回缓存数据

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) { //key为nil return nil
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache... 先根据key查找内存缓存
    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]; //这里new operation,是为了使用NSOperation的取消方法,通过设置取消方法来达到取消异步从磁盘中读取缓存的操作
    //开子线程 查找磁盘中的缓存
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) { //操作取消的话  直接return
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool { //根据key查找磁盘缓存
            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;
}

如果内存和磁盘都没有读取到缓存,则进行下载操作.

SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {

具体的下载代码,设置请求超时时间, 然后创建 NSMutableURLRequest 用于请求, 然后新建一个 SDWebImageDownloaderOperation 下载任务, 设置参数后, 讲下载任务加入下载队列下载.

//传入图片的URL,图片下载过程的Block回调,图片完成的Block回调
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;
    // 传入对应的参数,addProgressCallback: completedBlock: forURL: createCallback: 方法
    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
        __strong __typeof (wself) sself = wself;
        //超时时间 默认15秒
        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
        // Header的设置
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) 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;
        }
        //创建 SDWebImageDownloaderOperation,这个是下载任务的执行者。并设置对应的参数
        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) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

如果该 url 是第一次加载的话,那么就会执行 createCallback 这个回调block ,然后在 createCallback 里面开始构建网络请求,在下载过程中执行各类进度 block 回调

- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *(^)())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) { //url为nil block传回nil
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;
// dispatch_barrier_sync 是前面的任务结束后这个任务才执行, 这个执行完后下个才执行, 串行操作
    dispatch_barrier_sync(self.barrierQueue, ^{
        SDWebImageDownloaderOperation *operation = self.URLOperations[url]; //根据url取下载操作
        if (!operation) { //如果url之前没有下载操作, 即第一次加载, 则 createCallback
            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];

        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

图片下载完成后, 会回到完成的的 block 回调中做图片转换处理和缓存操作

 BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && image && !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];
                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:data forKey:key toDisk:cacheOnDisk];
                            }

                            dispatch_main_sync_safe(^{
                                if (!weakOperation.isCancelled) {
                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
                                }
                            });
                        });
                    }
                    else {
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
                        }

                        dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }

                if (finished) {
                    @synchronized (self.runningOperations) {
                        [self.runningOperations removeObject:operation];
                    }
                }

下载和解码缓存后, 就只剩最后一步,即回到UIImageView 控件的设置图片方法 block 回调中,给对应的 UIImageView 设置图片

dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    // 设置的不自动设置图片, 则通过block返回image
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    //设置图片
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                    if ((options & SDWebImageDelayPlaceholder)) {
                        // image下载不成功则设置占位图
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];

到此这个方法最关键的图片缓存和下载的代码结束了,本文完。

发布了19 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/aluoshiyi/article/details/88927653