SDWebImageソースコードの簡単な解析

7009 ワード

前言:SDWebImageはデザインと作成が非常に素晴らしいライブラリであり、ソースコードを読むと非常に収穫があります.全体的にruntime,gcdのシリアルと同時キュー,dispatch_を用いたbarrier_async,NSOperation,NSOperationQueue,autoreleasepool,NSCacheはメモリキャッシュなどを実現する.マルチスレッドの使用、ロックの使用、blockのコールバック、メモリ最適化、バックグラウンド実行タスクは非常に良い使用例です.

- (void)sd_setImageWithURL:(NSURL*)url以降の操作

  • [self sd_cancelCurrentImageLoad]; UIViewにバインドされているDictionaryで「UIImgeView ImageLoad」キーで取得したすべてを取得し、
  • のダウンロードをキャンセルします.
  • urlをimageviewにバインドする
  • 占有ピクチャがある場合、メインスレッド設定ピクチャ
     __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]を呼び出す.サブビューを再調整します.
  • SDWebImageManagerを介してsharedManagerはid operationダウンロードピクチャを作成し、blockコールバックしてピクチャを設定します.さらにoperationは「UIIMageView ImageLoad」キーでバインドされます.
  • ダウンロードプロセスSDWebImageCombinedOperationを作成する:NSObjectは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 
    // 
    - (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);
    

    1つのurlをダウンロードする場合、urlをkeyとするdictにurlを加えることで、既に存在する場合、ダウンロード中であることを示すと、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;
            }