SDWebImage 原始碼解析

J_Knight_發表於2017-12-25

相信對於廣大的iOS開發者,對SDWebImage並不會陌生,這個框架通過給UIImageView和UIButton新增分類,實現一個非同步下載圖片並且支援快取的功能。整個框架的介面非常簡潔,每個類的分工都很明確,是很值得大家學習的。

在使用這個框架的時候,只需要提供一個下載的url和佔點陣圖就可以在回撥裡拿到下載後的圖片:

[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(@"圖片載入完成");
        
}];
複製程式碼

而且我們還可以不設定佔點陣圖片,也可以不使用回撥的block,非常靈活:

//圖片下載完成後直接顯示下載後的圖片
[imageview sd_setImageWithURL:[NSURL URLWithString:@"pic.jpg"]];
複製程式碼

在最開始先簡單介紹這個框架:

這個框架的核心類是SDWebImageManger,在外部有UIImageView+WebCacheUIButton+WebCache 為下載圖片的操作提供介面。內部有SDWebImageManger負責處理和協調 SDWebImageDownloaderSDWebImageCacheSDWebImageDownloader負責具體的下載任務,SDWebImageCache負責關於快取的工作:新增,刪除,查詢快取。

首先我們大致看一下這個框架的呼叫流程圖:

SDWebImage

從這個流程圖裡可以大致看出,該框架分為兩個層:UIKit層(負責接收下載引數)和工具層(負責下載操作和快取)。

OK~基本流程大概清楚了,我們看一下每個層具體實現吧~


UIKit層

該框架最外層的類是UIImageView +WebCache,我們將圖片的URL,佔點陣圖片直接給這個類。下面是這個類的公共介面:

 // ==============  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;
複製程式碼

可以看出,這個類提供的介面非常靈活,可以根據我們自己的需求來呼叫其中某一個方法,而這些方法到最後都會走到:

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

而這個方法裡面,呼叫的是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的分類裡呢? 因為SDWebImage框架也支援UIButton的下載圖片等方法,所以需要在它們的父類:UIView裡面統一一個下載方法。

簡單看一下這個方法的實現(省略的程式碼用...代替):

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

    //valid key:UIImageView || UIButton
    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    //UIView+WebCacheOperation 的 operationDictionary
    //下面這行程式碼是保證沒有當前正在進行的非同步下載操作, 使它不會與即將進行的操作發生衝突
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    

    //新增臨時的佔點陣圖(在不延遲新增佔點陣圖的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能在主執行緒進行
            dispatch_main_async_safe(^{
                
                if (!sself) {
                    return;
                }
               
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                     //image,而且不自動替換 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,表示當前的操作正在進行
        [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);
            }
        });
    }
    ```



> 值得一提的是,在這一層,使用一個字典``operationDictionary``專門用作儲存操作的快取,隨時新增,刪除操作任務。
而這個字典是``UIView+WebCacheOperation``分類的關聯物件,它的存取方法使用執行時來操作:


```objc
 // ==============  UIView+WebCacheOperation.m ============== //
 //獲取關聯物件:operations(用來存放操作的字典)
- (SDOperationsDictionary *)operationDictionary {
    SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
    //存放操作的字典
    if (operations) {
        return operations;
    }
    
    //如果沒有,就新建一個
    operations = [NSMutableDictionary dictionary];
    
    objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    return operations;
}
複製程式碼

為什麼不直接在UIImageView+WebCache裡直接關聯這個物件呢?我覺得這裡作者應該是遵從物件導向的單一職責原則(SRP:Single responsibility principle),就連類都要履行這個職責,何況分類呢?這裡作者專門創造一個分類UIView+WebCacheOperation來管理操作快取(字典)。

到這裡,UIKit層上面的東西都講完了,現在開始正式講解工具層。

工具層

上文提到過,SDWebImageManager同時管理SDImageCacheSDWebImageDownloader兩個類,它是這一層的老大哥。在下載任務開始的時候,SDWebImageManager首先訪問SDImageCache來查詢是否存在快取,如果有快取,直接返回快取的圖片。如果沒有快取,就命令SDWebImageDownloader來下載圖片,下載成功後,存入快取,顯示圖片。以上是SDWebImageManager大致的工作流程。

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

 // ==============  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;//記錄當前正在執行的操作
複製程式碼

SDWebImageManager下載圖片的方法只有一個:

[SDWebImageManager.sharedManager loadImageWithURL:options:progress:completed:]
複製程式碼

看一下這個方法的具體實現:

 // ==============  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) {
        
        ...
        //(沒有快取圖片) || (即使有快取圖片,也需要更新快取圖片) || (代理沒有響應imageManager:shouldDownloadImageForURL:訊息,預設返回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 用來標記當前的下載任務,便於被取消
            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. 如果任務被取消,則什麼都不做,避免和其他的completedBlock重複
                
                } else if (error) {
                    
                    //2. 如果有錯誤
                    //2.1 在completedBlock裡傳入error
                    [self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

							//2.2 在錯誤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 {
                    
                    //3. 下載成功
                    //3.1 如果需要下載失敗後重新下載,則將當前url從失敗url名單裡移除
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    //3.2 進行快取
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                   
                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                    
                        //(即使快取存在,也要重新整理圖片) && 快取圖片 && 不存在下載後的圖片:不做操作
                                           
                    } 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];
                            }
                            //將圖片傳入completedBlock
                            [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];
                }
            }];
            
            //取消的block
            operation.cancelBlock = ^{
            
                //取消當前的token
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                //從當前執行的操作列表裡移除當前操作
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        
        } else if (cachedImage) {
            
            //存在快取圖片
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            
            //呼叫完成的block
            [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;
           
            // 呼叫完成的block
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            
            //刪去當前的下載操作
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;                                                             
}
複製程式碼

看完了SDWebImageManager的回撥處理,我們分別看一下 SDImageCacheSDWebImageDownloader內部具體是如何工作的。首先看一下SDImageCache

SDImageCache

屬性

 // ==============  SDImageCache.m ============== //
@property (strong, nonatomic, nonnull) NSCache *memCache;//記憶體快取
@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;
    }
		
    //================檢視記憶體的快取=================//
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    
    // 如果存在,直接呼叫block,將image,data,CaheType傳進去
    if (image) {
    
        NSData *diskData = nil;
        
        //如果是gif,就拿到data,後面要傳到doneBlock裡。不是gif就傳nil
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        
        //因為圖片有快取可供使用,所以不用例項化NSOperation,直接範圍nil
        return nil;
    }

    //================檢視磁碟的快取=================//
    NSOperation *operation = [NSOperation new];
    
    //唯一的子執行緒:self.ioQueue
    dispatch_async(self.ioQueue, ^{
        
        if (operation.isCancelled) {
            // 在用之前就判斷operation是否被取消了,作者考慮的非常嚴謹
            return;
        }

        @autoreleasepool {
            
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                
                // cost 被用來計算快取中所有物件的代價。當記憶體受限或者所有快取物件的總代價超過了最大允許的值時,快取會移除其中的一些物件。
                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;
}
複製程式碼

SDWebImageDownloader

屬性

 // ==============  SDWebImageDownloader.m ============== //
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;//下載佇列
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;//最後新增的下載操作
@property (assign, nonatomic, nullable) Class operationClass;//操作類
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;//運算元組
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;//HTTP請求頭
@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
        
        //建立下載請求
        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用於請求網路資源的操作,它是一個 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];
        }
        
        //優先順序
        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
            //addDependency:引數opertaion倍新增到NSOperationQueue後,只有等該opertion結束後才能執行其他的operation,實現了後進先出
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}
複製程式碼

這裡面還有一個addProgressCallback: progressBlock: completedBlock: forURL: createCallback:方法,用來儲存progressBlockcompletedBlock。我們看一下這個方法的實現:

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

    // url 用來作為回撥字典的key,如果為空,立即返回失敗 
    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    //序列化前面所有的操作
    dispatch_barrier_sync(self.barrierQueue, ^{
    
        	//當前下載操作中取出SDWebImageDownloaderOperation例項
        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 預設是一個字典,存放 progressBlock 和 completedBlock
        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

複製程式碼

這裡真正儲存兩個block的方法是addHandlersForProgress: completed:

- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                            completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    //例項化一個SDCallbacksDictionary,存放一個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的核心方法都講解完畢了,其他沒有講到的部分以後會慢慢新增上去。

最後看一下一些比較零散的知識點:


1. 執行時存取關聯物件:

存:

objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
//將operations物件關聯給self,地址為&loadOperationKey,語義是OBJC_ASSOCIATION_RETAIN_NONATOMIC。
複製程式碼

取:

SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//將operations物件通過地址&loadOperationKey從self裡取出來
複製程式碼

2. 陣列的寫操作需要加鎖(多執行緒訪問,避免覆寫)


//給self.runningOperations加鎖
//self.runningOperations陣列的新增操作
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }

//self.runningOperations陣列的刪除操作
- (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. 設定不能為nil的引數

- (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;
}
複製程式碼

如果在引數裡新增了nonnull關鍵字,那麼編譯器就可以檢查傳入的引數是否為nil,如果是,則編譯器會有警告

5. 容錯,強制轉換型別

if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
}
複製程式碼

在傳入的引數為NSString時(但是方法引數要求是NSURL),自動轉換為NSURL


貌似還有圖片解碼等內容沒有詳細看,以後會逐漸補充噠~

本文已經同步到我的個人技術部落格:傳送門

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

注意注意!!!

筆者在近期開通了個人公眾號,主要分享程式設計,讀書筆記,思考類的文章。

  • 程式設計類文章:包括筆者以前釋出的精選技術文章,以及後續釋出的技術文章(以原創為主),並且逐漸脫離 iOS 的內容,將側重點會轉移到提高程式設計能力的方向上。
  • 讀書筆記類文章:分享程式設計類思考類心理類職場類書籍的讀書筆記。
  • 思考類文章:分享筆者平時在技術上生活上的思考。

因為公眾號每天釋出的訊息數有限制,所以到目前為止還沒有將所有過去的精選文章都發布在公眾號上,後續會逐步釋出的。

而且因為各大部落格平臺的各種限制,後面還會在公眾號上釋出一些短小精幹,以小見大的乾貨文章哦~

掃下方的公眾號二維碼並點選關注,期待與您的共同成長~

公眾號:程式設計師維他命

相關文章