通讀SDWebImage②--視圖分類

来源:http://www.cnblogs.com/Mike-zh/archive/2016/02/22/5205223.html
-Advertisement-
Play Games

本文目錄 UIView+WebCacheOperation UIImageView+WebCache、UIImageView+HighlightedWebCache、MKAnnotationView+WebCache UIButton+WebCache 對於視圖分類,我們最熟悉的當屬 這個分類了。通


本文目錄

對於視圖分類,我們最熟悉的當屬UIImageView+WebCache這個分類了。通常在為一個UIImageView設置一張網路圖片並讓SD自動緩存起來就會使用這個分類下的- (void)sd_setImageWithURL:(NSURL *)url;方法,如果想要設置占點陣圖,則使用了可以傳遞占點陣圖的方法。本文會從這個方法入手介紹一些視圖分類的使用。首先我們要看一下SD中有關視圖的幾個分類:

UIView+WebCacheOperation // 將操作與視圖綁定和取消綁定
UIImageView+WebCache // 對UIImageView設置網路圖片,實現非同步下載、顯示、同時實現緩存
UIImageView+HighlightedWebCache // 與UIImageView+WebCache的功能完全一致,只是將image設置給UIImageView的highlightedImage屬性而不是image屬性
MKAnnotationView+WebCache // 與UIImageView+WebCache的功能完全一致,只是將image設置給了MKAnnotationView的image屬性
UIButton+WebCache // 功能很強大,可以設置不同的state的BackgroundImage或者Image

下麵我們先看一下所有的視圖分類都依賴的UIView的分類:UIView+WebCacheOperation
回到頂部

UIView+WebCacheOperation

為方便找到和管理視圖的正在進行的一些操作,SD將每一個視圖的實例和它正在進行的操作(下載和緩存的組合操作)綁定起來,實現操作和視圖的一一對應關係,以便可以隨時拿到視圖正在進行的操作,控制其取消等。

具體的實現是使用runtime給UIView綁定了一個屬性,這個屬性的key是static char loadOperationKey的地址,
這個屬性是NSMutableDictionary類型,value為操作,key是針對不同類型的視圖和不同類型的操作設定的字元串

為什麼要使用static char loadOperationKey的地址作為屬性的key,實際上很多第三方框架在給類綁定屬性的時候都會使用這種方案(如AFN),這樣做有以下幾個好處:

1.占用空間小,只有一個位元組。
2.靜態變數,地址不會改變,使用地址作為key總是唯一的且不變的。
3.避免和其他框架定義的key重覆,或者其他key將其覆蓋的情況。比如在其他文件(仍然是UIView的分類)中定義了同名同值的key,使用objc_setAssociatedObject進行設置綁定的屬性的時候,可能會將在別的文件中設置的屬性值覆蓋。

UIView+WebCacheOperation這個分類提供了三個方法,用於操作綁定關係。

// 返回綁定的屬性
- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key;

// 對綁定的字典屬性setObject
- (void)sd_cancelImageLoadOperationWithKey:(NSString *)key;

// 對綁定的字典屬性removeObject
- (void)sd_removeImageLoadOperationWithKey:(NSString *)key;

需要註意對綁定值setObject的時候的一些細節:

- (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key {
    // 若這個key對應的操作本來就有且正在執行,那麼先將這個操作取消,並將它移除。
    [self sd_cancelImageLoadOperationWithKey:key];
    // 然後設置新的操作
    NSMutableDictionary *operationDictionary = [self operationDictionary];
    [operationDictionary setObject:operation forKey:key];
}
回到頂部

UIImageView+WebCache、UIImageView+HighlightedWebCache、MKAnnotationView+WebCache

這一部雖然標題設置了三個分類,但是我們主要講解UIImageView+WebCache,在本文的開始就說到,另外兩個分類的實現是完全一致的,而且代碼重覆度為99%(絲毫沒有誇張)。
UIImageView+WebCache,最熟悉的就是以下幾個為UIImage設置圖片網路的方法:

- sd_setImageWithURL:
- sd_setImageWithURL: placeholderImage:
- sd_setImageWithURL: placeholderImage: options:

- sd_setImageWithURL: completed:
- sd_setImageWithURL: placeholderImage: completed:
- sd_setImageWithURL: placeholderImage: options: completed:
- sd_setImageWithURL: placeholderImage: options: progress: completed:

- sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed:

但無論是使用哪個方法,它們的實現上都是調用了- sd_setImageWithURL: placeholderImage: options: progress: completed:方法,只是傳遞的參數不同。(插語:帶方法描述的語言就是麻煩,省略參數做起來都複雜)。

下麵我們看一下- sd_setImageWithURL: placeholderImage: options: progress: completed:方法的實現:

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    [self sd_cancelCurrentImageLoad]; // 移除UIImageView當前綁定的操作。這一句非常關鍵,當在TableView的cell包含了的UIImageView被重用時,首先調用這一行代碼,保證這個ImageView的下載和緩存組合操作都被取消。如果①上次賦值的圖片正在下載,則下載不再進行;②下載完成了,但還沒有執行到調用回調(回調包含wself.image = image) ,由於操作被取消,因而不會顯示和重用的cell相同的圖片;③以上兩種情況只有在網速極慢和手機處理速度極慢的情況下才會發生,實際上發生的概率非常小,大多數是這種情況:操作已經進行到下載完成了,這次使用的cell是一個重用的cell,而且保留著imageView的image,對於這種情況SD會用下麵的設置占點陣圖的語句,將image暫時設置為占點陣圖,如果占點陣圖為空,就意味著先暫時清空image。
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // 將傳入的url與self綁定
    
    // 如果沒有設置延遲載入占點陣圖,設置image為占點陣圖
    // 這句代碼要結合上面的理解,實際上在這個地方SD埋了一個bug,如果設置了SDWebImageDelayPlaceholder選項,會忽略占點陣圖,而如果imageView在重用的cell中,這時會顯示重用著的image。
    // 我建議將下麵的兩句改為
    /*
        if (!(options & SDWebImageDelayPlaceholder)) {
            dispatch_main_async_safe(^{
                self.image = placeholder;
            });
        } else {
            dispatch_main_async_safe(^{
                self.image = nil;
            });
        }
    */    
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }
    if (url) {
        // 檢查是否通過`setShowActivityIndicatorView:`方法設置了顯示正在載入指示器。如果設置了,使用`addActivityIndicator`方法向self添加指示器
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }
        
        __weak __typeof(self) wself = self;
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            [wself removeActivityIndicator]; // 移除載入指示器
            if (!wself) return;
            dispatch_main_sync_safe(^{
                if (!wself) return;
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                { // 如果設置了禁止自動設置image選項,則不會執行`wself.image = image;`,而是直接執行完成回調,有用戶自己決定如何處理。
                    completedBlock(image, error, cacheType, url);
                    return;
                }
                else if (image) {
                    // 設置image
                    wself.image = image;
                    [wself setNeedsLayout];
                } else { // image為空,並且設置了延遲設置占點陣圖,會將占點陣圖設置為最終的image
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
                if (completedBlock && finished) { 
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        // 為UIImageView綁定新的操作
        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else { // 判斷url不存在,移除載入指示器,執行完成回調,傳遞錯誤信息。
        dispatch_main_async_safe(^{
            [self removeActivityIndicator];
            if (completedBlock) {
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}

這個就是完整的載入網路圖片的過程,而具體的如何實現下載細節、網路訪問驗證、在下載完成之後如何進行記憶體和磁碟緩存的,請參照上一篇文章的內容。

上面的所有的為UIImageView設置網路圖片的方法中有一個和其他稍微不同的- sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: completed:,其實也就是張的有點不同,它的實現是這樣的:

- (void)sd_setImageWithPreviousCachedImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
    UIImage *lastPreviousCachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
    
    [self sd_setImageWithURL:url placeholderImage:lastPreviousCachedImage ?: placeholder options:options progress:progressBlock completed:completedBlock];    
}

可以看到,它的思路是先取得上次緩存的圖片,然後作為占點陣圖的參數再次進行一次圖片設置。

在設置圖片的過程中,有關如何移除和添加載入指示器的兩個方法,我們這裡不做討論,其實是對系統的UIActivityIndicatorView視圖的使用。

還有一個需要的方法- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs,要註意的是這個方法傳遞的參數是一個由URL組成的數組,這個方法用來設置UIImage的animationImages屬性。它的實現思路是:
將遍歷URL數組中的元素,根據每個URL創建一個下載操作並執行,在回調裡面對imationImages屬性值追加下載好的image。它的具體實現如下:

- (void)sd_setAnimationImagesWithURLs:(NSArray *)arrayOfURLs {
    [self sd_cancelCurrentAnimationImagesLoad];
    __weak __typeof(self)wself = self;

    NSMutableArray *operationsArray = [[NSMutableArray alloc] init];

    for (NSURL *logoImageURL in arrayOfURLs) {
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:logoImageURL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            if (!wself) return;
            dispatch_main_sync_safe(^{
                __strong UIImageView *sself = wself;
                [sself stopAnimating]; // 先動畫停止
                if (sself && image) {
                    NSMutableArray *currentImages = [[sself animationImages] mutableCopy];
                    if (!currentImages) {
                        currentImages = [[NSMutableArray alloc] init];
                    }
                    [currentImages addObject:image]; // 追加新下載的image

                    sself.animationImages = currentImages;
                    [sself setNeedsLayout];
                }
                [sself startAnimating];
            });
        }];
        [operationsArray addObject:operation];
    }
    // 註意這裡綁定的不是單個操作,而是操作數據。UIView+WebCacheOperation的方法`sd_cancelImageLoadOperationWithKey:`也對操作數組做了適配
    [self sd_setImageLoadOperation:[NSArray arrayWithArray:operationsArray] forKey:@"UIImageViewAnimationImages"];
}
回到頂部

UIButton+WebCache

有關UIButton+WebCache分類中的方功能確實強大:可以為image的不同state(Normal、Highlighted、Disabled、Selected)設置不同的backgoud圖片或者image圖片,但是它的實現很簡單,幾乎和上面介紹的UIImageView的設置方法是相同的,只是UIButton多了一個管理不同state下的url的功能。

UIButton管理圖片的url其實也是通過runtime綁定屬性來實現的,和UIImageView不同的是:UIImageView只需一張圖片所以就綁定了NSURL類型值,而UIButton需要多張圖片且要區分state,所以使用NSMutableDictionary來存儲圖片的URL,其中key是@(state),value是該state對應的圖片的url。需要註意的是它只是存儲了image的URL,而並沒有存儲backgroudImage的URL。

- (NSMutableDictionary *)imageURLStorage {
    NSMutableDictionary *storage = objc_getAssociatedObject(self, &imageURLStorageKey);
    if (!storage)
    {
        storage = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, &imageURLStorageKey, storage, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    return storage;
}

- sd_setImageWithURL: forState: placeholderImage: options: completed:對它的調用:

[self.imageURLStorage removeObjectForKey:@(state)];

self.imageURLStorage[@(state)] = url;

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • <script type="text/javascript"> (function($){ window.addEventListener("click",function(evt){ for(var i in evt) { console.log(i + "---" + evt[i]); } },
  • 前段時間看了大神的博客文章【使用Flexible實現手淘H5頁面的終端適配】(地址:http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html),受益良多,寫了個小demo,記錄一下以防忘記,需要註意幾點,如下: 1. meta
  • 效果圖: 閃屏頁用到了handler和CountDownTimer類,還需配置一下Activity的主題,這裡是:android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 全屏主題的意思。 實現源碼: package com.example
  • 當一個iOS應用被送到後臺,它的主線程會被暫停。你用NSThread的detachNewThreadSelector:toTar get:withObject:類方法創建的線程也被掛起了。如果你想在後臺完成一個長期任務,就必須調用UIApplication的beginBackgroundTaskWi
  • 近期剛剛學習了一種多線程技術,現結合自己的理解將其羅列出來,希望能夠與大家交流一下,多線程是一種能夠節省程式運算時間的方法,大大的提高了程式的運算效率,那麼首先我們來說一下進程和線程概念: 一個程式包含一個以上的進程,而一個進程又可以包含一個以上的線程,每一個進程都有自己獨立的記憶體空間,相應的一個進
  • 1.啟用藍牙並使設備處於可發現狀態 1.1 在使用BluetoothAdapter類的實例進操作之前,應啟用isEnable()方法檢查設備是否啟用了藍牙適配器。 // 使用意圖提示用戶啟用藍牙,並使設備處於可發現狀態 private void startBluetooth() { Bluetoot
  • 分類:C#、Android、VS2015; 創建日期:2016-02-23 一、簡介 這一章我們主要學習Intent的基本用法,並通過例子演示如下功能: 如何啟動另一個界面; 如何獲取另一個界面的返回值; 如何利用Intent讀取圖庫中的圖片; 如何利用Intent讀取和更新通訊錄; 如何利用Int...
  • 做蘋果開發也有段很長的時間了,斷斷續續大概已經4年了【目前沒有從事這個行業】,從剛開始在北京的一家培訓公司學習iOS開發起,到找到工作,再到丟掉工作,失去信心,再到重回開發。過程複雜。今天總結一下一些常用的蘋果電腦操作和開發環境XCODE以及終端的常用命令的一些操作知識。 首先總結一下蘋果系統的操作
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...