iOS復(fù)習(xí)中有關(guān)SDWebImage可能知識(shí)點(diǎn)總結(jié)(1)

1. SDWebImage內(nèi)部實(shí)現(xiàn)原理步驟

1.1 實(shí)現(xiàn)步驟

1. 入口setImageWithUrl:placeHolderImage:options:會(huì)把placeHolderImage顯示,然后SDWebImageManager根據(jù)URL開始處理圖片.

2. 進(jìn)入SDWebImageManager-downloadWithURL:delegate:options:userInfo:交給SDImageCache從緩存查找圖片是否已經(jīng)下載queryDiskCacheForKey:delegate:userInfo:

3. 先從內(nèi)存圖片緩存查找是否有圖片,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate回調(diào)imageCache:didFineImage:forKey:userInfo:到SDWebImageManager.

4. SDWebImageManagerDelegate回調(diào)webImageManager:didFinishWithImage:到UIImageView + WebCache等前端展示圖片.

5. 如果內(nèi)存緩存中沒有,生成NSInvocationOperation添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存

6. 根據(jù)URLKey在硬盤緩存目錄下嘗試讀取圖片文件.這一步是在NSOperation進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào)notifyDelegate.

7. 如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小 會(huì)先清空內(nèi)存緩存).SDImageCacheDelegate 回調(diào)imageCache:didFinishImage:forKey:userInfo:進(jìn)而回調(diào)展示圖片.

8. 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調(diào)imageCache:didNotFindImageForKey:userInfo.

9. 共享或重新生成一個(gè)下載器SDWebImageDownLoader開始下載圖片

10. 圖片下載由NSURLConnection來做,實(shí)現(xiàn)相關(guān)delegate來判斷圖片下載中,下載完成和下載失敗

11. connection:didReceiveData:中利用ImageIO做了按圖片下載進(jìn)度加載效果

12. connectionDidFinishLoading:數(shù)據(jù)下載完成后交給SDWebImageDecoder做圖片解碼處理

13. 圖片解碼處理在一個(gè)NSOperationQueue完成,不會(huì)拖慢主線程UI.如果有需要對(duì)下載的圖片進(jìn)行二次處理,最好也在這里完成,效率會(huì)好很多.

14. 在主線程notifyDelegateOnMainThreadWithInfo:宣告解碼完成imageDecoder:didFinishDecodingImage:userInfo:回調(diào)給SDWebImageDownloader

15. imageDownLoader:didFinishWithImage:回調(diào)給SDWebImageManager告知圖片下載完成

16. 通知所有的downloadDelegates下載完成,回調(diào)給需要的地方展示圖片

17. 將圖片保存到SDImageCache中內(nèi)存緩存和硬盤緩存同時(shí)保存,寫文件到硬盤也在以單獨(dú)NSInvocationOperation完成,避免拖慢主線程

18. SDImageCache在初始化的時(shí)候會(huì)注冊(cè)一些消息通知,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過期圖片

19. SDWI也提供UIButton + WebCache和MKAnnptation + WebCache方便使用

20. SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用

再用一張圖說明:

1.2 API中參數(shù)枚舉類型

1.2.1 SDWebImageOptions:圖片下載策略

例如,SD為UIImageView提供的UIImageView+WebCache.m分類,有這些API:

- (void)sd_setImageWithURL:(NSURL *)url {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}

- (void)sd_setImageWithURL:(NSURL *)url completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}

其實(shí),以上這些API都直接或間接利用到了SDWebImageOptions這個(gè)參數(shù),那么你記得這個(gè)參數(shù)有哪些類型?各有什么作用?

通過查看SDWebImageManager.h源代碼,可知如下:

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {

    // 默認(rèn)情況下,當(dāng)URL下載失敗時(shí),URL會(huì)被列入黑名單,導(dǎo)致庫(kù)不會(huì)再去重試,該標(biāo)記用于禁用黑名單
    SDWebImageRetryFailed = 1 << 0,

    // 默認(rèn)情況下,圖片下載開始于UI交互,該標(biāo)記禁用這一特性,這樣下載延遲到UIScrollView減速時(shí)
    SDWebImageLowPriority = 1 << 1,

    // 該標(biāo)記禁用磁盤緩存
    SDWebImageCacheMemoryOnly = 1 << 2,

    // 該標(biāo)記啟用漸進(jìn)式下載,圖片在下載過程中是漸漸顯示的,如同瀏覽器一下。
    // 默認(rèn)情況下,圖像在下載完成后一次性顯示
    SDWebImageProgressiveDownload = 1 << 3,

    // 即使圖片緩存了,也期望HTTP響應(yīng)cache control,并在需要的情況下從遠(yuǎn)程刷新圖片。
    // 磁盤緩存將被NSURLCache處理而不是SDWebImage,因?yàn)镾DWebImage會(huì)導(dǎo)致輕微的性能下載。
    // 該標(biāo)記幫助處理在相同請(qǐng)求URL后面改變的圖片。如果緩存圖片被刷新,則完成block會(huì)使用緩存圖片調(diào)用一次
    // 然后再用最終圖片調(diào)用一次
    SDWebImageRefreshCached = 1 << 4,

    // 在iOS 4+系統(tǒng)中,當(dāng)程序進(jìn)入后臺(tái)后繼續(xù)下載圖片。這將要求系統(tǒng)給予額外的時(shí)間讓請(qǐng)求完成
    // 如果后臺(tái)任務(wù)超時(shí),則操作被取消
    SDWebImageContinueInBackground = 1 << 5,

    // 通過設(shè)置NSMutableURLRequest.HTTPShouldHandleCookies = YES;來處理存儲(chǔ)在NSHTTPCookieStore中的cookie
    SDWebImageHandleCookies = 1 << 6,

    // 允許不受信任的SSL認(rèn)證
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

    // 默認(rèn)情況下,圖片下載按入隊(duì)的順序來執(zhí)行。該標(biāo)記將其移到隊(duì)列的前面,
    // 以便圖片能立即下載而不是等到當(dāng)前隊(duì)列被加載
    SDWebImageHighPriority = 1 << 8,

    // 默認(rèn)情況下,占位圖片在加載圖片的同時(shí)被加載。該標(biāo)記延遲占位圖片的加載直到圖片已以被加載完成
    SDWebImageDelayPlaceholder = 1 << 9,

    // 通常我們不調(diào)用動(dòng)畫圖片的transformDownloadedImage代理方法,因?yàn)榇蠖鄶?shù)轉(zhuǎn)換代碼可以管理它。
    // 使用這個(gè)票房則不任何情況下都進(jìn)行轉(zhuǎn)換。
    SDWebImageTransformAnimatedImage = 1 << 10,
};
1.2.2 SDImageCacheType:圖片緩存策略

例如,設(shè)置圖片的兩個(gè)例子

[self.image2 sd_setImageWithURL:imagePath2 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        NSLog(@"這里可以在圖片加載完成之后做些事情");
}];
//使用默認(rèn)圖片,而且用block 在完成后做一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        NSLog(@"圖片加載完成后做的事情");
}];

還有獲取下載進(jìn)度的例子

//使用默認(rèn)圖片,而且用block 在完成后做一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        NSLog(@"圖片加載完成后做的事情"); 
}];

上面的例子,都有個(gè)SDImageCacheTyp的參數(shù),你記得這個(gè)參數(shù)有哪些?

  • SDImageCacheType
//定義Cache類型
typedef NS_ENUM(NSInteger, SDImageCacheType) {
//不使用cache獲得圖片,依然會(huì)從web下載圖片
    SDImageCacheTypeNone,
//圖片從disk獲得
    SDImageCacheTypeDisk,
//圖片從Memory中獲得  
    SDImageCacheTypeMemory
};

2. 最大緩存和時(shí)間設(shè)置

  • SDImageCache類的源碼
//這個(gè)變量默認(rèn)值為YES,顯示比較高質(zhì)量的圖片,但是會(huì)浪費(fèi)比較多的內(nèi)存,可以通過設(shè)置NO來緩解內(nèi)存
@property (assign, nonatomic) BOOL shouldDecompressImages;
//總共的內(nèi)存允許圖片的消耗值
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//圖片存活于內(nèi)存的時(shí)間初始化的時(shí)候默認(rèn)為一周
@property (assign, nonatomic) NSInteger maxCacheAge;
//每次存儲(chǔ)圖片大小的限制
@property (assign, nonatomic) NSUInteger maxCacheSize;

  • 設(shè)置maxCacheSize的例子
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager.imageCache setMaxMemoryCost:1000000];//設(shè)置總緩存大小,默認(rèn)為0沒有限制
[manager.imageCache setMaxCacheSize:640000];//設(shè)置單個(gè)圖片限制大小
[manager.imageDownloader setMaxConcurrentDownloads:1];//設(shè)置同時(shí)下載線程數(shù),這是下載器的內(nèi)容,下面將會(huì)介紹
[manager downloadImageWithURL:[NSURL URLWithString:@"http://p9.qhimg.com/t01eb74a44c2eb43193.jpg"]
                      options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                          NSLog(@"%lu", receivedSize);
                      } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                          self.imageView1.image = image;
                          
                      }];
[manager downloadImageWithURL:[NSURL URLWithString:@"http://img.article.pchome.net/00/28/33/87/pic_lib/wm/kuanpin12.jpg"]
                      options:SDWebImageProgressiveDownload progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                          NSLog(@"%lu", receivedSize);
                      } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
                          self.imageView2.image = image;
                          
                      }];
NSUInteger size = [manager.imageCache getSize];
NSUInteger count = [manager.imageCache getDiskCount];
NSLog(@"size = %lu", size); // 644621(兩張測(cè)試圖片)
NSLog(@"count = %lu", count); // 2
[manager.imageCache clearDisk];
size = [manager.imageCache getSize];
count = [manager.imageCache getDiskCount];
NSLog(@"sizeClean = %lu", size);  //  0
NSLog(@"countClean = %lu", count);     //  0   這里使用的是clear

3. 區(qū)分:三種種緩存(內(nèi)存圖片緩存,磁盤圖片緩存,內(nèi)存操作緩存)

  • 先查看內(nèi)存圖片緩存,內(nèi)存圖片緩存沒有,后生成操作,查看磁盤圖片緩存
  • 磁盤圖片緩存有,就加載到內(nèi)存緩存,沒有就下載圖片
  • 在建立下載操作之前,判斷下載操作是否存在
  • 默認(rèn)情況下,下載的圖片數(shù)據(jù)會(huì)同時(shí)緩存到內(nèi)存和磁盤中

關(guān)于緩存位置

  • 內(nèi)存緩存是通過 NSCache的子類AutoPurgeCache來實(shí)現(xiàn)的;
  • 磁盤緩存是通過 NSFileManager 來實(shí)現(xiàn)文件的存儲(chǔ)(默認(rèn)路徑為/Library/Caches/default/com.hackemist.SDWebImageCache.default),是異步實(shí)現(xiàn)的。

關(guān)于圖片下載操作

SDWebImage的大部分工作是由緩存對(duì)象SDImageCache和異步下載器管理對(duì)象SDWebImageManager來完成的。

SDWebImage的圖片下載是由SDWebImageDownloader這個(gè)類來實(shí)現(xiàn)的,它是一個(gè)異步下載管理器,下載過程中增加了對(duì)圖片加載做了優(yōu)化的處理。而真正實(shí)現(xiàn)圖片下載的是自定義的一個(gè)Operation操作,將該操作加入到下載管理器的操作隊(duì)列downloadQueue中,Operation操作依賴系統(tǒng)提供的NSURLConnection類實(shí)現(xiàn)圖片的下載。

4. 高清和低清圖片與網(wǎng)絡(luò)環(huán)境的問題

  • 網(wǎng)絡(luò)判斷的問題--利用AFNetworking的API
    首先,啟用監(jiān)控
// AppDelegate.m 文件中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
    // 監(jiān)控網(wǎng)絡(luò)狀態(tài)
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
}

然后,在需要的地方獲取監(jiān)控管理

    // 以下代碼在需要監(jiān)聽網(wǎng)絡(luò)狀態(tài)的方法中使用
    AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
    if (mgr.isReachableViaWiFi)     { // 在使用Wifi, 下載原圖
    
    } else     { // 其他,下載小圖
        
    }
  • 并不能簡(jiǎn)單的這樣:WIFI就下載高清圖,蜂窩網(wǎng)絡(luò)就下載縮略圖。要考慮和利用緩存的因素。

  • 一個(gè)典型例子

- setItem:(CustomItem *)item
{
    _item = item;
    
     // 占位圖片
    UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];
    
    // 從內(nèi)存\沙盒緩存中獲得原圖,
    UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];
    if (originalImage) { // 如果內(nèi)存\沙盒緩存有原圖,那么就直接顯示原圖(不管現(xiàn)在是什么網(wǎng)絡(luò)狀態(tài))
        self.imageView.image = originalImage;
    } else { // 內(nèi)存\沙盒緩存沒有原圖
        AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
        if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下載原圖
            [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
        } else if (mgr.isReachableViaWWAN) { // 在使用手機(jī)自帶網(wǎng)絡(luò)
            //     用戶的配置項(xiàng)假設(shè)利用NSUserDefaults存儲(chǔ)到了沙盒中
            //    [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];
            //    [[NSUserDefaults standardUserDefaults] synchronize];
#warning 從沙盒中讀取用戶的配置項(xiàng):在3G\4G環(huán)境是否仍然下載原圖
            BOOL alwaysDownloadOriginalImage = [[NSUserDefaults standardUserDefaults] boolForKey:@"alwaysDownloadOriginalImage"];
            if (alwaysDownloadOriginalImage) { // 下載原圖
                [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
            } else { // 下載小圖
                [self.imageView sd_setImageWithURL:[NSURL URLWithString:item.thumbnailImage] placeholderImage:placeholder];
            }
        } else { // 沒有網(wǎng)絡(luò)
            UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];
            if (thumbnailImage) { // 內(nèi)存\沙盒緩存中有小圖
                self.imageView.image = thumbnailImage;
            } else { // 處理離線狀態(tài),而且有沒有緩存時(shí)的情況
                self.imageView.image = placeholder;
            }
        }
    }
}

5. 可能的問題

5.1 后臺(tái)沒有處理的高清大圖導(dǎo)致APP內(nèi)存過大而奔潰?

5.2 SD怎樣解決Cell移出屏幕后的cell中的UIImageView繼續(xù)下載問題?-- 移除UIImageView當(dāng)前綁定的操作。

  • SD為設(shè)置UIImageView提供的API,歸根結(jié)底調(diào)用的是下面API:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
  • 它的方法實(shí)現(xiàn)體中,第一句話就是[self sd_cancelCurrentImageLoad];
  • 這個(gè)非常關(guān)鍵,當(dāng)在TableView的cell包含了的UIImageView被重用時(shí),首先調(diào)用這一行代碼,保證這個(gè)ImageView的下載和緩存組合操作都被取消。如果:
    ①上次賦值的圖片正在下載,則下載不再進(jìn)行;
    ②下載完成了,但還沒有執(zhí)行到調(diào)用回調(diào)(回調(diào)包含wself.image = image),由于操作被取消,因而不會(huì)顯示和重用的cell相同的圖片;
    ③以上兩種情況只有在網(wǎng)速極慢和手機(jī)處理速度極慢的情況下才會(huì)發(fā)生,實(shí)際上發(fā)生的概率非常小,大多數(shù)是這種情況:操作已經(jīng)進(jìn)行到下載完成了,這次使用的cell是一個(gè)重用的cell,而且保留著imageView的image,對(duì)于這種情況SD會(huì)在該實(shí)現(xiàn)方法里面接著設(shè)置占位圖的語句,將image暫時(shí)設(shè)置為占位圖,如果占位圖為空,就意味著先暫時(shí)清空image。
  • SD內(nèi)部移除綁定的操作的調(diào)用棧為:
- (void)sd_cancelCurrentImageLoad {
    [self sd_cancelImageLoadOperationWithKey:@"UIImageViewImageLoad"];
}
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key {
    // Cancel in progress downloader from queue
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    id operations = [operationDictionary objectForKey:key];
    if (operations) {
        if ([operations isKindOfClass:[NSArray class]]) {
            for (id <SDWebImageOperation> operation in operations) {
                if (operation) {
                    [operation cancel];
                }
            }
        } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
            [(id<SDWebImageOperation>) operations cancel];
        }
        [operationDictionary removeObjectForKey:key];
    }
}

5.3 SDWebImage怎么實(shí)現(xiàn)屏幕滑動(dòng)時(shí),暫停數(shù)據(jù)源的任務(wù)?通過NSOperationQueue的setSuspend嗎?

3.1 基于NSURLConnection的SDWebImage

(至少2014年7月的版本)老版本的基于 NSURLConnectionSDWebImage 是通過這樣的機(jī)制:NSURLConnection工作在主線程,雖然NSURLConnection工作在子線程,但因?yàn)閁I相關(guān)的操作和回調(diào)中的setImage都在同一個(gè)主線程,滑動(dòng)屏幕會(huì)導(dǎo)致主線程的runloop切換mode為UITrackingRunLoopMode,此時(shí)原來kCFRunLoopDefaultMode上的source (這里是NSURLConnectionsetImage) 都被暫停,runloop執(zhí)行不到回調(diào)。

它的本意是不讓網(wǎng)絡(luò)相關(guān)的操作阻塞到主線程,改正:網(wǎng)絡(luò)相關(guān)的操作在子線程,主線程runloop的mode切換并不會(huì)影響子線程,但是它這樣設(shè)計(jì)的確有這樣的效果:屏幕滑動(dòng)時(shí),暫停數(shù)據(jù)下載的任務(wù),改正:滑動(dòng)屏幕并不會(huì)暫停數(shù)據(jù)下載,暫停的是同一個(gè)主線程的setImage。

在老版本中,在SDWebImageDownloaderOperation.m文件中有這樣一段話:

SDWebImageDownloaderOperation.m

3.2 基于NSURLSession的SDWebImage

然而,新版本的 SDWebImage 是基于 NSURLSession 的,這個(gè)NSURLSession不同于NSURLConnection的最大區(qū)別是不是基于主線程 子線程 的runloop控制的,而是通過NSOperation新開子線程,所以同意主線程的runloop切換mode并不會(huì)影響子線程的操作。所以,新版本的SDWebImage是沒有這個(gè)“滑動(dòng)即暫?!钡男Ч?/del>。改正:同樣,滑動(dòng)屏幕并不會(huì)暫停數(shù)據(jù)下載,暫停的是同一個(gè)主線程的setImage。

如果,實(shí)在有需要,有兩種辦法,可以自己改寫setImage的方法,在里面設(shè)置工作的mode,同老版的SDWebImage一樣改正:一種是改變setImage的線程或者mode。還有一種辦法,可以監(jiān)聽ScrollView的拉拽狀態(tài),當(dāng)ScrollView的代理方法監(jiān)聽到被拉拽,就suspend操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容