SDWebImage源碼解析


簡(jiǎn)書博客已經(jīng)暫停更新殉农,想看更多技術(shù)博客請(qǐng)到:

  • 掘金 :J_Knight_
  • 個(gè)人博客: J_Knight_
  • 個(gè)人公眾號(hào):程序員維他命

相信對(duì)于廣大的iOS開發(fā)者蛛砰,對(duì)SDWebImage并不會(huì)陌生吨拗,這個(gè)框架通過給UIImageView和UIButton添加分類,實(shí)現(xiàn)一個(gè)異步下載圖片并且支持緩存的功能。整個(gè)框架的接口非常簡(jiǎn)潔,每個(gè)類的分工都很明確状知,是很值得大家學(xué)習(xí)的。

在使用這個(gè)框架的時(shí)候孽查,只需要提供一個(gè)下載的url和占位圖就可以在回調(diào)里拿到下載后的圖片:

[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
        
        imageview.image = image;
        NSLog(@"圖片加載完成");
        
    }];

而且我們還可以不設(shè)置占位圖片饥悴,也可以不使用回調(diào)的block,非常靈活:

//圖片下載完成后直接顯示下載后的圖片
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"]];

在最開始先簡(jiǎn)單介紹這個(gè)框架:

這個(gè)框架的核心類是SDWebImageManger盲再,在外部有UIImageView+WebCacheUIButton+WebCache 為下載圖片的操作提供接口西设。內(nèi)部有SDWebImageManger負(fù)責(zé)處理和協(xié)調(diào) SDWebImageDownloaderSDWebImageCacheSDWebImageDownloader負(fù)責(zé)具體的下載任務(wù),SDWebImageCache負(fù)責(zé)關(guān)于緩存的工作:添加答朋,刪除贷揽,查詢緩存。

首先我們大致看一下這個(gè)框架的調(diào)用流程圖:

SDWebImage

從這個(gè)流程圖里可以大致看出梦碗,該框架分為兩個(gè)層:UIKit層(負(fù)責(zé)接收下載參數(shù))和工具層(負(fù)責(zé)下載操作和緩存)禽绪。

OK~基本流程大概清楚了蓖救,我們看一下每個(gè)層具體實(shí)現(xiàn)吧~


UIKit層

該框架最外層的類是UIImageView +WebCache,我們將圖片的URL印屁,占位圖片直接給這個(gè)類循捺。下面是這個(gè)類的公共接口:

 // ==============  UIImageView + WebCache.h ============== //
- (void)sd_setImageWithURL:(nullable NSURL *)url;

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

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

- (void)sd_setImageWithURL:(nullable NSURL *)url
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

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

可以看出,這個(gè)類提供的接口非常靈活雄人,可以根據(jù)我們自己的需求來調(diào)用其中某一個(gè)方法从橘,而這些方法到最后都會(huì)走到:

// ==============  UIView+ WebCache.m ============== //
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

而這個(gè)方法里面,調(diào)用的是UIView+WebCache分類的:

// ==============  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;

為什么不是UIImageView+WebCache而要上一層到UIView的分類里呢柠衍?
因?yàn)镾DWebImage框架也支持UIButton的下載圖片等方法洋满,所以需要在它們的父類:UIView里面統(tǒng)一一個(gè)下載方法。

簡(jiǎn)單看一下這個(gè)方法的實(shí)現(xiàn)(省略的代碼用...代替):

 // ==============  UIView+ WebCache.m ============== //

    //valid key:UIImageView || UIButton
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //UIView+WebCacheOperation 的 operationDictionary
    //下面這行代碼是保證沒有當(dāng)前正在進(jìn)行的異步下載操作, 使它不會(huì)與即將進(jìn)行的操作發(fā)生沖突
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    

    //添加臨時(shí)的占位圖(在不延遲添加占位圖的option下)
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    //如果url存在
    if (url) {
       
       ...
        __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) {
          
            ...
            //dispatch_main_sync_safe : 保證block能在主線程進(jìn)行
            dispatch_main_async_safe(^{
                
                if (!sself) {
                    return;
                }
               
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                     //image珍坊,而且不自動(dòng)替換 placeholder image
                    completedBlock(image, error, cacheType, url);
                    return;
                    
                } else if (image) {
                    //存在image牺勾,需要馬上替換 placeholder image
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                
                } else {                    
                    //沒有image,在圖片下載完之后顯示 placeholder image
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        
        //在操作緩存字典(operationDictionary)里添加operation阵漏,表示當(dāng)前的操作正在進(jìn)行
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
        
    } else {
        //如果url不存在驻民,就在completedBlock里傳入error(url為空)
        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);
            }
        });
    }
    ```



> 值得一提的是,在這一層履怯,使用一個(gè)字典``operationDictionary``專門用作存儲(chǔ)操作的緩存回还,隨時(shí)添加,刪除操作任務(wù)叹洲。
而這個(gè)字典是``UIView+WebCacheOperation``分類的關(guān)聯(lián)對(duì)象柠硕,它的存取方法使用運(yùn)行時(shí)來操作:


```objc
 // ==============  UIView+WebCacheOperation.m ============== //
 //獲取關(guān)聯(lián)對(duì)象:operations(用來存放操作的字典)
- (SDOperationsDictionary *)operationDictionary {
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    //存放操作的字典
    if (operations) {
        return operations;
    }
    
    //如果沒有,就新建一個(gè)
    operations = [NSMutableDictionary dictionary];
    
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}

為什么不直接在UIImageView+WebCache里直接關(guān)聯(lián)這個(gè)對(duì)象呢运提?我覺得這里作者應(yīng)該是遵從面向?qū)ο蟮?strong>單一職責(zé)原則(SRP:Single responsibility principle)蝗柔,就連類都要履行這個(gè)職責(zé),何況分類呢民泵?這里作者專門創(chuàng)造一個(gè)分類UIView+WebCacheOperation來管理操作緩存(字典)癣丧。

到這里,UIKit層上面的東西都講完了栈妆,現(xiàn)在開始正式講解工具層胁编。

工具層

上文提到過,SDWebImageManager同時(shí)管理SDImageCacheSDWebImageDownloader兩個(gè)類鳞尔,它是這一層的老大哥嬉橙。在下載任務(wù)開始的時(shí)候,SDWebImageManager首先訪問SDImageCache來查詢是否存在緩存寥假,如果有緩存憎夷,直接返回緩存的圖片。如果沒有緩存昧旨,就命令SDWebImageDownloader來下載圖片拾给,下載成功后祥得,存入緩存,顯示圖片蒋得。以上是SDWebImageManager大致的工作流程级及。

在詳細(xì)講解SDWebImageManager是如何下載圖片之前,我們先看一下這個(gè)類的幾個(gè)重要的屬性:

 // ==============  SDWebImageManager.h ============== //
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;//管理緩存
@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader //下載器*imageDownloader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;//記錄失效url的名單
@property (strong, nonatomic, nonnull) NSMutableArray<SDWebImageCombinedOperation *> *runningOperations;//記錄當(dāng)前正在執(zhí)行的操作

SDWebImageManager下載圖片的方法只有一個(gè):

[SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]

看一下這個(gè)方法的具體實(shí)現(xiàn):

 // ==============  SDWebImageManager.m ============== //
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock {
     ...                             
    //在SDImageCache里查詢是否存在緩存的圖片
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        
        ...
        //(沒有緩存圖片) || (即使有緩存圖片额衙,也需要更新緩存圖片) || (代理沒有響應(yīng)imageManager:shouldDownloadImageForURL:消息饮焦,默認(rèn)返回yes,需要下載圖片)|| (imageManager:shouldDownloadImageForURL:返回yes窍侧,需要下載圖片)
        if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            
            //1. 存在緩存圖片 && 即使有緩存圖片也要下載更新圖片
            if (cachedImage && options & SDWebImageRefreshCached) {
                [self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }


            // 2. 如果不存在緩存圖片
            ...
            
            //開啟下載器下載
            //subOperationToken 用來標(biāo)記當(dāng)前的下載任務(wù)县踢,便于被取消
            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) {
                    // 1. 如果任務(wù)被取消,則什么都不做伟件,避免和其他的completedBlock重復(fù)
                
                } else if (error) {
                    
                    //2. 如果有錯(cuò)誤
                    //2.1 在completedBlock里傳入error
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

                            //2.2 在錯(cuò)誤url名單中添加當(dāng)前的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 {
                    
                    //3. 下載成功
                    //3.1 如果需要下載失敗后重新下載硼啤,則將當(dāng)前url從失敗url名單里移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    //3.2 進(jìn)行緩存
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                   
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                    
                        //(即使緩存存在,也要刷新圖片) && 緩存圖片 && 不存在下載后的圖片:不做操作
                                           
                    } else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
                        
          //(下載圖片成功 && (沒有動(dòng)圖||處理動(dòng)圖) && (下載之后斧账,緩存之前處理圖片)               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];
                            }
                            //將圖片傳入completedBlock
                            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        
                        //(圖片下載成功并結(jié)束)
                        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];
                    }
                }

                     //如果完成谴返,從當(dāng)前運(yùn)行的操作列表里移除當(dāng)前操作
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            
            //取消的block
            operation.cancelBlock = ^{
            
                //取消當(dāng)前的token
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                //從當(dāng)前運(yùn)行的操作列表里移除當(dāng)前操作
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        
        } else if (cachedImage) {
            
            //存在緩存圖片
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            
            //調(diào)用完成的block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            
            //刪去當(dāng)前的的下載操作(線程安全)
            [self safelyRemoveOperationFromRunning:operation];
        
        } else {
            
            //沒有緩存的圖片,而且下載被代理終止了
            __strong __typeof(weakOperation) strongOperation = weakOperation;
           
            // 調(diào)用完成的block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            
            //刪去當(dāng)前的下載操作
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;                                                             
}

看完了SDWebImageManager的回調(diào)處理咧织,我們分別看一下
SDImageCacheSDWebImageDownloader內(nèi)部具體是如何工作的嗓袱。首先看一下SDImageCache

SDImageCache

屬性

 // ==============  SDImageCache.m ============== //
@property (strong, nonatomic, nonnull) NSCache *memCache;//內(nèi)存緩存
@property (strong, nonatomic, nonnull) NSString *diskCachePath;//磁盤緩存路徑
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;//
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t //ioQueue唯一子線程;

核心方法:查詢緩存

 // ==============  SDImageCache.m ============== //
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
   
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }
        
    //================查看內(nèi)存的緩存=================//
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    // 如果存在,直接調(diào)用block习绢,將image渠抹,data,CaheType傳進(jìn)去
    if (image) {
    
        NSData *diskData = nil;
        
        //如果是gif闪萄,就拿到data梧却,后面要傳到doneBlock里。不是gif就傳nil
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        
        //因?yàn)閳D片有緩存可供使用桃煎,所以不用實(shí)例化NSOperation,直接范圍nil
        return nil;
    }

    //================查看磁盤的緩存=================//
    NSOperation *operation = [NSOperation new];
    
    //唯一的子線程:self.ioQueue
    dispatch_async(self.ioQueue, ^{
        
        if (operation.isCancelled) {
            // 在用之前就判斷operation是否被取消了大刊,作者考慮的非常嚴(yán)謹(jǐn)
            return;
        }

        @autoreleasepool {
            
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                
                // cost 被用來計(jì)算緩存中所有對(duì)象的代價(jià)为迈。當(dāng)內(nèi)存受限或者所有緩存對(duì)象的總代價(jià)超過了最大允許的值時(shí),緩存會(huì)移除其中的一些對(duì)象缺菌。
                NSUInteger cost = SDCacheCostForImage(diskImage);
                
                //存入內(nèi)存緩存中
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

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

    return operation;
}

SDWebImageDownloader

屬性

 // ==============  SDWebImageDownloader.m ============== //
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;//下載隊(duì)列
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;//最后添加的下載操作
@property (assign, nonatomic, nullable) Class operationClass;//操作類
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;//操作數(shù)組
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;//HTTP請(qǐng)求頭
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;//用來阻塞前面的下載線程(串行化)

核心方法:下載圖片

 // ==============  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;
        
        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
        
        //創(chuàng)建下載請(qǐng)求
        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;
        }
        
        //創(chuàng)建下載操作:SDWebImageDownloaderOperation用于請(qǐng)求網(wǎng)絡(luò)資源的操作葫辐,它是一個(gè) NSOperation 的子類
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        //url證書
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
        //優(yōu)先級(jí)
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        //在下載隊(duì)列里添加下載操作,執(zhí)行下載操作
        [sself.downloadQueue addOperation:operation];
        
        //如果后進(jìn)先出
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            //addDependency:參數(shù)opertaion倍添加到NSOperationQueue后伴郁,只有等該opertion結(jié)束后才能執(zhí)行其他的operation耿战,實(shí)現(xiàn)了后進(jìn)先出
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

這里面還有一個(gè)addProgressCallback: progressBlock: completedBlock: forURL: createCallback:方法,用來保存progressBlockcompletedBlock焊傅。我們看一下這個(gè)方法的實(shí)現(xiàn):

 // ==============  SDWebImageDownloader.m ============== //
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {

    // url 用來作為回調(diào)字典的key剂陡,如果為空狈涮,立即返回失敗 
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    //串行化前面所有的操作
    dispatch_barrier_sync(self.barrierQueue, ^{
    
            //當(dāng)前下載操作中取出SDWebImageDownloaderOperation實(shí)例
        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];
        //這里 downloadOperationCancelToken 默認(rèn)是一個(gè)字典鸭栖,存放 progressBlock 和 completedBlock
        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

這里真正保存兩個(gè)block的方法是addHandlersForProgress: completed:

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    //實(shí)例化一個(gè)SDCallbacksDictionary歌馍,存放一個(gè)progressBlock 和 completedBlock
    SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
    if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
    if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
    dispatch_barrier_async(self.barrierQueue, ^{
        //添加到緩存中 self.callbackBlocks
        [self.callbackBlocks addObject:callbacks];
    });
    return callbacks;
}

到這里SDWebImage的核心方法都講解完畢了,其他沒有講到的部分以后會(huì)慢慢添加上去晕鹊。

最后看一下一些比較零散的知識(shí)點(diǎn):


1. 運(yùn)行時(shí)存取關(guān)聯(lián)對(duì)象:

存:

objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//將operations對(duì)象關(guān)聯(lián)給self松却,地址為&loadOperationKey,語(yǔ)義是OBJC_ASSOCIATION_RETAIN_NONATOMIC溅话。

认汀:

SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//將operations對(duì)象通過地址&loadOperationKey從self里取出來

2. 數(shù)組的寫操作需要加鎖(多線程訪問,避免覆寫)


//給self.runningOperations加鎖
//self.runningOperations數(shù)組的添加操作
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

//self.runningOperations數(shù)組的刪除操作
- (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
    @synchronized (self.runningOperations) {
        if (operation) {
            [self.runningOperations removeObject:operation];
        }
    }
}

3. 確保在主線程的宏:

dispatch_main_async_safe(^{
                 //將下面這段代碼放在主線程中
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });

//宏定義:
#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

4. 設(shè)置不能為nil的參數(shù)

- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
    if ((self = [super init])) {
        _imageCache = cache;
        _imageDownloader = downloader;
        _failedURLs = [NSMutableSet new];
        _runningOperations = [NSMutableArray new];
    }
    return self;
}

如果在參數(shù)里添加了nonnull關(guān)鍵字飞几,那么編譯器就可以檢查傳入的參數(shù)是否為nil砚哆,如果是,則編譯器會(huì)有警告

5. 容錯(cuò)循狰,強(qiáng)制轉(zhuǎn)換類型

if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
}

在傳入的參數(shù)為NSString時(shí)(但是方法參數(shù)要求是NSURL)窟社,自動(dòng)轉(zhuǎn)換為NSURL


貌似還有圖片解碼等內(nèi)容沒有詳細(xì)看,以后會(huì)逐漸補(bǔ)充噠~

本文已經(jīng)同步到我的個(gè)人技術(shù)博客:傳送門绪钥,歡迎常來^^


本文已在版權(quán)印備案灿里,如需轉(zhuǎn)載請(qǐng)?jiān)L問版權(quán)印。48422928

獲取授權(quán)

-------------------------------- 2018年7月17日更新 --------------------------------

注意注意3谈埂O坏酢!

筆者在近期開通了個(gè)人公眾號(hào)寸潦,主要分享編程色鸳,讀書筆記,思考類的文章见转。

  • 編程類文章:包括筆者以前發(fā)布的精選技術(shù)文章命雀,以及后續(xù)發(fā)布的技術(shù)文章(以原創(chuàng)為主),并且逐漸脫離 iOS 的內(nèi)容斩箫,將側(cè)重點(diǎn)會(huì)轉(zhuǎn)移到提高編程能力的方向上吏砂。
  • 讀書筆記類文章:分享編程類思考類乘客,心理類狐血,職場(chǎng)類書籍的讀書筆記。
  • 思考類文章:分享筆者平時(shí)在技術(shù)上易核,生活上的思考匈织。

因?yàn)楣娞?hào)每天發(fā)布的消息數(shù)有限制,所以到目前為止還沒有將所有過去的精選文章都發(fā)布在公眾號(hào)上,后續(xù)會(huì)逐步發(fā)布的缀匕。

而且因?yàn)楦鞔蟛┛推脚_(tái)的各種限制纳决,后面還會(huì)在公眾號(hào)上發(fā)布一些短小精干,以小見大的干貨文章哦~

掃下方的公眾號(hào)二維碼并點(diǎn)擊關(guān)注弦追,期待與您的共同成長(zhǎng)~

公眾號(hào):程序員維他命
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末岳链,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劲件,更是在濱河造成了極大的恐慌掸哑,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件零远,死亡現(xiàn)場(chǎng)離奇詭異苗分,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牵辣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門摔癣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纬向,你說我怎么就攤上這事择浊。” “怎么了逾条?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵琢岩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我师脂,道長(zhǎng)担孔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任吃警,我火速辦了婚禮糕篇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酌心。我一直安慰自己拌消,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布安券。 她就那樣靜靜地躺著墩崩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪完疫。 梳的紋絲不亂的頭發(fā)上泰鸡,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天债蓝,我揣著相機(jī)與錄音壳鹤,去河邊找鬼。 笑死饰迹,一個(gè)胖子當(dāng)著我的面吹牛芳誓,可吹牛的內(nèi)容都是我干的余舶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼锹淌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匿值!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赂摆,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挟憔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后烟号,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绊谭,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年汪拥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了达传。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迫筑,死狀恐怖宪赶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脯燃,我是刑警寧澤搂妻,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站曲伊,受9級(jí)特大地震影響叽讳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜坟募,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一岛蚤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懈糯,春花似錦涤妒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至屿储,卻和暖如春贿讹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背够掠。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工民褂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓赊堪,卻偏偏與公主長(zhǎng)得像面殖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哭廉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容