通讀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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...