SDWebImage學習

SDWebImage學習

github託管地址:https://github.com/rs/SDWebImage
導入頭文件:UIImageView+WebCache.h


使用sd_setImageWithURL:緩存圖片:

1 . 方法sd_setImageWithURL:

//簡單的加載url中的圖片
[self.image sd_setImageWithURL:url]

2 . 方法 sd_setImageWithURL: placeholderImage:

//加載完成之前使用圖片placeImage作爲默認值
[self.image1 sd_setImageWithURL:url placeholderImage:placeImage];

3 . 方法sd_setImageWithURL:imagePath2 completed:

//在圖片加載完成之後執行block
[self.image2 sd_setImageWithURL:imagePath2 completed:
 ^(UIImage *image, NSError *error,
   SDImageCacheType cacheType, NSURL *imageURL) {
    NSLog(@"Do something in here...");
}];

4 . 方法sd_setImageWithURL:placeholderImage:options:

//options表示緩存方式
[self.image sd_setImageWithURL:url
              placeholderImage:placeImage
                       options:SDWebImageRetryFailed];
options每個選項含義:

    //加載失敗後重試
    SDWebImageRetryFailed = 1 << 0,
    //UI交互期間延時下載
    SDWebImageLowPriority = 1 << 1,
    //只進行內存緩存
    SDWebImageCacheMemoryOnly = 1 << 2,
    //逐步下載,顯示圖像也是逐步的
    SDWebImageProgressiveDownload = 1 << 3,
    //刷新緩存
    SDWebImageRefreshCached = 1 << 4,
    //後臺下載
    SDWebImageContinueInBackground = 1 << 5,
    //通過設置NSMutableURLRequest.HTTPShouldHandleCookies = YES
    //把cookies存儲在NSHTTPCookieStore
    SDWebImageHandleCookies = 1 << 6,
    //允許使用無效的SSL證書
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    //優先下載
    SDWebImageHighPriority = 1 << 8,
    //延遲顯示佔位符
    SDWebImageDelayPlaceholder = 1 << 9,
    //進行任意圖形變換
    SDWebImageTransformAnimatedImage = 1 << 10,

SDWebImage中我們使用較多的是它提供的UIImageView分類,支持從遠程服務器下載並緩存圖片。從iOS5.0開始,NSURLCache也可以處理磁盤緩存,那麼SDWebImage有什麼優勢?首先,NSURLCache是緩存原始數據(raw data)到磁盤或內存,因此每次使用的時候需要將原始數據轉換成具體的對象,如UIImage等,這會導致額外的數據解析以及內存佔用等,而SDWebImage則是緩存UIImage對象在內存,緩存在NSCache中,同時直接保存壓縮過的圖片到磁盤中;其次,當你第一次在UIImageView中使用image對象的時候,圖片的解碼是在主線程中運行的!而SDWebImage會強制將解碼操作放到子線程中。下圖是SDWebImage簡單的類圖關係:
SDWebImage一些類的調用關係

圖片加載調用函數:

- (void)sd_setImageWithURL:(NSURL *)url 
          placeholderImage:(UIImage *)placeholder 
                   options:(SDWebImageOptions)options 
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock 
                 completed:(SDWebImageCompletionBlock)completedBlock {

    //取消正在下載的操作
    [self sd_cancelCurrentImageLoad];
    //創建對象self和對象url的關聯
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    /*...*/

    if (url) {
        //防止循環引用
        __weak UIImageView *wself = self;
        //由SDWebImageManager負責圖片的獲取
        id <SDWebImageOperation> 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"];
    } else {
        /*...*/
    }
}

由以上函數可知圖片是從服務端、內存或者硬盤獲取是由SDWebImageManager管理的,這個類有幾個重要的屬性:

//負責管理cache,涉及內存緩存和硬盤保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//負責從網絡下載圖片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;

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

- (id <SDWebImageOperation>)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])) {
            if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                    //如果圖片找到了,但是採用的SDWebImageRefreshCached選項,
                    //通知獲取到了圖片,並再次從網絡下載,使NSURLCache重新刷新
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

                /*下載選項設置...*/
                //使用imageDownloader開啓網絡下載
                id <SDWebImageOperation> subOperation =
                [self.imageDownloader downloadImageWithURL:url
                                                   options:downloaderOptions
                                                  progress:progressBlock
                                                 completed:^(UIImage *downloadedImage,
                                                             NSData *data, NSError *error,
                                                             BOOL finished) {
                            /*...*/

                            if (downloadedImage && finished) {
                            //下載完成後,先將圖片保存到imageCache中,然後主線程返回
                                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;
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = 
            wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        //SDWebImageDownloaderOperation派生自NSOperation,負責圖片下載工作
        operation = [[wself.operationClass alloc]
                     initWithRequest:request
                     options:options
                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {/*...*/}
                     completed:^(UIImage *image, NSData *data, 
                                 NSError *error, BOOL finished) {/*...*/}
                     cancelled:^{/*...*/}];
            /*...*/}];

    return operation;
}

SDWebImageDownloaderOperation派生自NSOperation,通過NSURLConnection進行圖片的下載,爲了確保能夠處理下載的數據,需要在後臺運行runloop:

- (void)start {
    @synchronized (self) {

        /*...*/
#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.thread = [NSThread currentThread];
    }

    [self.connection start];

    if (self.connection) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        /*廣播通知*/
        [[NSNotificationCenter defaultCenter]
         postNotificationName:SDWebImageDownloadStartNotification object:self];

        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            //在默認模式下運行當前runlooprun,直到調用CFRunLoopStop停止運行
            CFRunLoopRun();
        }

        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection
            didFailWithError:[NSError errorWithDomain:NSURLErrorDomain
                                                 code:NSURLErrorTimedOut
                                             userInfo:
                    @{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain 
                                                              code:0 
                                                          userInfo:
            @{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#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的緩存:

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        //停止當前的runLoop
        CFRunLoopStop(CFRunLoopGetCurrent());
        /*...*/
    }
    /*...*/
    if (completionBlock) {
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
            completionBlock(nil, nil, nil, YES);
        }
        else {
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] 
                            cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];

            if (!image.images) {
                //圖片解碼
                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];
}
發佈了29 篇原創文章 · 獲贊 6 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章