SDWebImage源碼簡要解析


前言: SDWebImage是一個設計和編寫都非常精妙的一個庫,源碼讀下來非常的有收獲??傮w上運用了runtime, gcd的串行和并發隊列,dispatch_barrier_async,NSOperation,NSOperationQueue ,autoreleasepool,NSCache實現內存緩存等。對多線程的使用,鎖的使用,block的回調,內存優化,后臺運行任務都是非常好的使用案例。

- (void)sd_setImageWithURL:(NSURL *)url 之后的操作

  1. [self sd_cancelCurrentImageLoad];
    獲取綁定在UIView上的Dictionary中以”UIImageViewImageLoad”key獲取的所有 <SDWebImageOperation> ,并取消下載

  2. 將url綁定到該imageview上

  3. 如果有占位圖片,在主線程設置圖片

     __weak __typeof(self)wself = self; 
     if (!wself) return;
             dispatch_main_sync_safe(^{
                 if (!wself) return;
                 if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                 {
                     completedBlock(image, error, cacheType, url);
                     return;
                 }
                 else if (image) {
                     wself.image = image;
                     [wself setNeedsLayout];
                 }
    

    }
    設置弱引用,并反復檢查,設置后調用 [wself setNeedsLayout];
    重新調整子視圖。

  4. 通過SDWebImageManager.sharedManager 創建一個 id <SDWebImageOperation> operation下載圖片,以block回調并設置圖片。并且將operation通過”UIImageViewImageLoad”key綁定。

  5. 下載過程
    創建一個 SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
    檢查url是否在failedURLs里 @synchronized (self.failedURLs) {},如果在則執行下載失敗block并返回

 @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    } 添加operation到下載隊列

查詢緩存中是否存在該圖片,如果有則返回,并從隊列中移除operation,在返回圖片前要檢查operation是否被取消,如果取消說明url變了,顯然不能再被返回。查詢緩存會新建一個operation并返回。

 NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{  //self.ioQueue是一個串行queue
        if (operation.isCancelled) {
            return;
        }
//autoreleasepool用于及時釋放內存
        @autoreleasepool {
//從文件中讀取data并默認對圖片進行解碼,支持gif和webp格式,解碼也是新建一個autoreleasepool
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

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

重點說一下 內存中的緩存,通過NSCache實現

FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
    return image.size.height * image.size.width * image.scale * image.scale;
}
 [self.memCache setObject:diskImage forKey:key cost:cost]
self.memCache 是 AutoPurgeCache : NSCache, 會在系統內存報警時自動移除所有緩存。

如果緩存中沒有圖片則進行下載:

      _downloadQueue = [NSOperationQueue new];
        _downloadQueue.maxConcurrentOperationCount = 6;
        _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
        _URLCallbacks = [NSMutableDictionary new];
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
        _downloadTimeout = 15.0;

//新建一個operation
SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
//執行下載
- (void)start { }
// 允許后臺下載
 self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

                if (sself) {
                    [sself cancel];

                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
 self.thread = [NSThread currentThread];
- (void)cancel {
    @synchronized (self) {
        if (self.thread) {
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
//下載完回調
__block NSArray *callbacksForURL;
                                                            dispatch_barrier_sync(sself.barrierQueue, ^{
                                                                callbacksForURL = [sself.URLCallbacks[url] copy];
                                                                if (finished) {
                                                                    [sself.URLCallbacks removeObjectForKey:url];
                                                                }
                                                            });
                                                            for (NSDictionary *callbacks in callbacksForURL) {
                                                                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                                                                if (callback) callback(image, data, error, finished);
                                                            }

通過dispatch_barrier_sync來隔離并發隊列中的讀和寫
queue會等待正在執行的block的執行完畢再執行。所以在這點上就保證了我們的barrier block 只有它自己在執行。所有的在他之后提交的block會一直等待到這個barrier block 執行完再執行。需要特別注意的是,傳入dispatch_barrier_async()函數的queue,必須是用dispatch_queue_create 創建的并發queue。如果是串行的queue或者是global concurrent queues ,這個函數就會變成 dispatch_async()了

dispatch_barrier_sync(self.barrierQueue,block);

對一個url進行下載時,通過將url加入一個以url為key的dict,如果已經存在,說明正在下載,則不會執行下載block,同時還有處理另外一個imageview請求同一個url的情況。下面的代碼非常巧妙的處理這種情況:

 dispatch_barrier_sync(self.barrierQueue, ^{
        BOOL first = NO;
        if (!self.URLCallbacks[url]) {
            self.URLCallbacks[url] = [NSMutableArray new];
            first = YES;
        }

        // Handle single download of simultaneous download request for the same URL
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
//將回調copy并存起來
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
// 加入回調數組中
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;

        if (first) {
            createCallback();
        }
    });

如果設置了operationQueue的LIFO,對operation添加依賴

if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency,The receiver is not considered ready to execute until all of its dependent operations have finished executing. If the receiver is already executing its task, adding dependencies has no practical effect. This method may change the isReady and dependencies properties of the receiver.如果已經在執行則無效,還沒執行,要等依賴執行完之后才會被執行。
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
        }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容