SDWebImage源碼剖析(一)

  在開發項目的過程當中會用到不少第三方庫,好比AFNetWorking,SDWebImage,FMDB等,但一直都沒去好好的研究一下,最近恰好項目不是太緊,閒下來能夠給本身充充電,先研究一下SDWebImage的底層實現,源碼地址:SDWebImage
  先介紹一下SDWebImage,咱們使用較多的是它提供的UIImageView分類,支持從遠程服務器下載並緩存圖片。自從iOS5.0開始,NSURLCache也能夠處理磁盤緩存,那麼SDWebImage的優點在哪?首先NSURLCache是緩存原始數據(raw data)到磁盤或內存,所以每次使用的時候須要將原始數據轉換成具體的對象,如UIImage等,這會致使額外的數據解析以及內存佔用等,而SDWebImage則是緩存UIImage對象在內存,緩存在NSCache中,同時直接保存壓縮過的圖片到磁盤中;還有一個問題是當你第一次在UIImageView中使用image對象的時候,圖片的解碼是在主線程中運行的!而SDWebImage會強制將解碼操做放到子線程中。下圖是SDWebImage簡單的類圖關係:git

SDWebImage.png
SDWebImage.png

下面從UIImageView的圖片加載開始看起,Let's go!github

首先咱們在給UIImageView設置圖片的時候會調用方法:緩存

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

其中url爲遠程圖片的地址,而placeholder爲預顯示的圖片。
其實還能夠添加一些額外的參數,好比圖片選項SDWebImageOptions服務器

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1

通常使用的是SDWebImageRetryFailed | SDWebImageLowPriority,下面看看具體的函數調用:網絡

- (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);//關聯該view對應的圖片URL  
   /*...*/ 
    if (url) {
        __weak UIImageView *wself = self;//防止retain cricle
        //由SDWebImageManager負責圖片的獲取
        id  operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
              /*獲取圖片到主線層顯示*/ 
        }];
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } 
}

能夠看出圖片是從服務端、內存或者硬盤獲取是由SDWebImageManager管理的,這個類有幾個重要的屬性:app

@property (strong, nonatomic, readwrite) SDImageCache *imageCache;//負責管理cache,涉及內存緩存和硬盤保存
@property (strong, nonatomic, readwrite) SDWebImageDownloader *imageDownloader;//負責從網絡下載圖片
@property (strong, nonatomic) NSMutableArray *runningOperations;//包含全部當前正在下載的操做對象

manager會根據URL先去imageCache中查找對應的圖片,若是沒有在使用downloader去下載,並在下載完成緩存圖片到imageCache,接着看實現:async

- (id )downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options
                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
 {
     /*...*/
    //根據URL生成對應的key,沒有特殊處理爲[url absoluteString];
    NSString *key = [self cacheKeyForURL:url];
    //去imageCache中尋找圖片
    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) 
    {
       /*...*/
       //若是圖片沒有找到,或者採用的SDWebImageRefreshCached選項,則從網絡下載
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
                dispatch_main_sync_safe(^{
                  //若是圖片找到了,可是採用的SDWebImageRefreshCached選項,通知獲取到了圖片,並再次從網絡下載,使NSURLCache從新刷新
                     completedBlock(image, nil, cacheType, YES, url);
                });
            }
            /*下載選項設置*/ 
            //使用imageDownloader開啓網絡下載
            id  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
                /*...*/
               if (downloadedImage && finished) {
                     //下載完成後,先將圖片保存到imageCache中,而後主線程返回
                     [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);
                            }
                        });
                    }
                }
          /*...*/
       }
        else if (image) {
          //在cache中找到圖片了,直接返回
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
        }
    }];
    return operation;
}

下面先看downloader從網絡下載的過程,下載是放在NSOperationQueue中進行的,默認maxConcurrentOperationCount爲6,timeout時間爲15s:函數

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __weak SDWebImageDownloader *wself = self;
    /*...*/
    //防止NSURLCache和SDImageCache重複緩存
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy :NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
    request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
    request.HTTPShouldUsePipelining = YES;
    request.allHTTPHeaderFields = wself.HTTPHeaders;//設置http頭部
    //SDWebImageDownloaderOperation派生自NSOperation,負責圖片下載工做
    operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request
                                                          options:options
                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {}
                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {}
                                                        cancelled:^{}];
    operation.shouldDecompressImages = wself.shouldDecompressImages;//是否須要解碼
    if (wself.username && wself.password) {
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
    }
    if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
    }
        [wself.downloadQueue addOperation:operation];
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // 若是下載順序是後面添加的先運行
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
        }
    }];
    return operation;
}

SDWebImageDownloaderOperation派生自NSOperation,經過NSURLConnection進行圖片的下載,爲了確保可以處理下載的數據,須要在後臺運行runloop:oop

- (void)start {
  /*...*/
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        //開啓後臺下載
        if ([self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            self.backgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;
                if (sself) {
                    [sself cancel];
                    [[UIApplication sharedApplication] endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        self.executing = YES;
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
    }
    [self.connection start];

    if (self.connection) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
       //註冊和發送通知須要在一個線程
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
       CFRunLoopRun();//在默認模式下運行當前runlooprun,直到調用CFRunLoopStop中止運行
        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

下載過程當中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中將接收到的數據保存到NSMutableData中,[self.imageData appendData:data],下載完成後在該線程完成圖片的解碼,並在完成的completionBlock中進行imageCache的緩存:post

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        CFRunLoopStop(CFRunLoopGetCurrent());//中止當前對runloop
        /*...*/
        if (completionBlock) {
            /*...*/
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];

              // Do not force decoding animated GIFs
             if (!image.images) {
                 if (self.shouldDecompressImages) {
                    image = [UIImage decodedImageWithImage:image];//圖片解碼
                }
            }
            if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
            }
            else {
                completionBlock(image, self.imageData, nil, YES);
            }
        }
    }
    self.completionBlock = nil;
    [self done];
}

後續的圖片緩存能夠參考:SDWebImage源碼剖析(二)

相關文章
相關標籤/搜索