本文轉載請註明出處 —— polobymulberry-博客園html
一直沒有系統地讀過整套源碼,就感受像一直看零碎的知識點,沒有系統讀過一本專業經典書籍同樣,會有點發虛,感受知識體系不健全!廢話少說,此次我決定好好閱讀下SDWebImage的源碼,個人閱讀方式,是帶着問題去閱讀源碼,而後強迫本身寫博客。git
既然是要帶着問題讀,那麼第一個問題就來了,SDWebImage是作什麼的?SDWebImage是一個開源的代碼庫,咱們能夠在github上找到它 —> Github傳送門。github
Github上是這樣介紹它的:web
This library provides a category for UIImageView with support for remote images coming from the web.緩存
因此咱們大概知道SDWebImage就是一個庫。這個庫本質是UIImageView的category。爲啥要作這個category呢?是爲了從服務器端遠程獲取圖片到UIImageView上顯示。固然,看完代碼後,就知道SDWebImage提供的功能遠不止說的這麼簡單。服務器
github上也給了一些例子,咱們看一下最經常使用的一個例子:網絡
1 #import <SDWebImage/UIImageView+WebCache.h> 2 3 ... 4 5 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 6 static NSString *MyIdentifier = @"MyIdentifier"; 7 8 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; 9 if (cell == nil) { 10 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 11 reuseIdentifier:MyIdentifier] autorelease]; 12 } 13 14 // Here we use the new provided sd_setImageWithURL: method to load the web image 15 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 16 placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; 17 18 cell.textLabel.text = @"My Text"; 19 return cell; 20 }
這確實是一個很常見的需求,就是在一個tableView上,每個cell都須要顯示網絡端獲取的image。好比咱們經常使用的新浪微博、網易新聞、知乎日報等等,都會用到。app
這裏最關鍵的一行代碼就是:dom
1 // Here we use the new provided sd_setImageWithURL: method to load the web image 2 [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] 3 placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
看到這裏,我不由自主地要讚歎兩句,這個接口設計的真的很棒!你想一想,我要從網絡端獲取圖片,並顯示到UIImageView上,其實我只要給你一個圖片的url就ok啦,另外當前圖片若是還未獲取到,怎麼辦?弄個placeholderImage唄(當網絡端圖片還未加載完成,做爲一個替代的圖片,好比一些app若是網絡很差的話,文章對應圖片加載不出來,就會顯示帶有「暫無圖片」的圖片)。async
其中的圖片如何獲取,如何緩存等等都屏蔽了。甚至沒有暴露從網絡端獲取到的是什麼圖片,固然後面咱們會提到SDWebImage中有其餘的藉口會暴露返回的圖片image,容許在image上操做後再賦值給imageView。
細想下其中的過程,我大致有一個簡單的實現概念(先本身想一想怎麼實現,而後對照實際源碼,這樣才能看到本身不足):
而後發出網絡請求,獲取圖片image
若是圖片獲取成功,賦值給UIImageView
帶着我這簡陋的想法,我模仿SDWebImage寫了以下代碼:
首先我建立了一個UIImageView的category —— UIImageView+Extension.h
主要是模仿SDWebImage的 sd_setImageWithURL:placeholderImage:函數寫了一個pjx_setImageWithURL:placeholderImage:
- UIImageView+Extension.h
1 #import <UIKit/UIKit.h> 2 3 @interface UIImageView (Extension) 4 5 - (void)pjx_setImageWithURL:(NSURL *)imageUrl placeholderImage:(UIImage *)placeholderImage; 6 7 @end
- UIImageView+Extension.m
1 #import "UIImageView+Extension.h" 2 3 @implementation UIImageView (Extension) 4 5 - (void)pjx_setImageWithURL:(NSURL *)imageUrl placeholderImage:(UIImage *)placeholderImage 6 { 7 // 1.先將UIImageView的image設爲placeholderImage 8 self.image = placeholderImage; 9 10 // 2.而後發出網絡請求,獲取圖片image 11 NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; 12 UIImage *image = [UIImage imageWithData:imageData]; 13 14 // 3.若是圖片獲取成功,賦值給UIImageView 15 if (image) { 16 self.image = image; 17 } 18 } 19 20 @end
ViewController調用代碼:
1 #import "ViewController.h" 2 #import "UIImageView+Extension.h" 3 4 @interface ViewController () 5 6 @property (weak, nonatomic) IBOutlet UIImageView *imageView; 7 8 @end 9 10 @implementation ViewController 11 12 #pragma mark - life cycle 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 NSString *baiduLogoString = @"https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png"; 17 18 [self.imageView pjx_setImageWithURL:[NSURL URLWithString:baiduLogoString] placeholderImage:[UIImage imageNamed:@"placeholderImage"]]; 19 } 20 21 @end
效果以下:
在沒有網絡(左)和有網絡(右)狀況下的對比圖
而後我喜滋滋地去看SDWebImage的sd_setImageWithURL:placeholderImage:實現,結果~~他竟然調用的是另一個巨多參數的函數:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock;
大概猜下,除了url和placeholder兩個參數懂是什麼意思,options不明白,progress確定表示的是下載的進度,也就是正在下載時候所要處理的事情(block),那麼completed應該表示的是下載完成後所要作的事(block)。
因而我定位到了該函數,發現本身徹底不是一個級別上的,看不懂。不過我還不死心,因而我全局搜索dataWithContentsOfURL,嗯,SDWebImage竟然沒有用!好吧,先無論了,不用就不用,那你總得給UIImageView的image賦值吧,並且確定要賦值一次placeholderImage和網絡請求獲得的image吧。果真找到了,就在上面那個巨多參數的函數中。我只截取了部分代碼
1 ...... 2 if (!(options & SDWebImageDelayPlaceholder)) { 3 dispatch_main_async_safe(^{ 4 self.image = placeholder; 5 }); 6 } 7 ..... 8 else if (image) { 9 wself.image = image; 10 [wself setNeedsLayout]; 11 } else { 12 if ((options & SDWebImageDelayPlaceholder)) { 13 wself.image = placeholder; 14 [wself setNeedsLayout]; 15 } 16 } 17 ...
能夠看到這裏實現了三處image的賦值。而且後面兩處賦值後當即使用setNeedsLayout來進行刷新(我註釋了刷新代碼,好像沒有發生什麼問題,不過這裏仍是注意一下,確定是某個情形下會發生沒法自動刷新圖片的狀況,纔要手動刷新)。好的,這裏咱們能夠停一下,看看這些image賦值都是發生在什麼狀況下的。
這幾處賦值都出現了SDWebImageDelayPlaceholder。看下它的註釋,首先,它是一個SDWebImageOptions枚舉值,而參數options也是一個枚舉類型的變量,註定二者是好基友了。話說回來,SDWebImageDelayPlaceholder表示的是什麼呢?看註釋:
/** * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */
翻譯過來就是,默認狀況下,當正在加載網絡端的image 時,placeholder已經加載到了UIImageView,這個枚舉項就是爲了不這種默認狀況,他將延遲placeholder的加載直到網絡端的image加載完成。可能有些抽象,看代碼就好了。
在還沒發送請求獲取網絡端圖片以前(即網絡端的image還沒加載),若是options中有SDWebImageDelayPlaceholder這一選項,就不給image賦值,若是沒有這一項,那麼就給image賦值placeholder。說白了就是下面這段代碼:
1 if (!(options & SDWebImageDelayPlaceholder)) { 2 dispatch_main_async_safe(^{ 3 self.image = placeholder; 4 }); 5 }
其中dispatch_main_async_safe就是SDWebImage定義的一個宏,很好理解:若是當前是主進程,就直接執行block,不然把block放到主進程運行。爲何要判斷是不是主進程?由於iOS上任何UI的操做都在主線程上執行,因此主進程還有一個名字,叫作「UI進程」。
1 #define dispatch_main_async_safe(block)\ 2 if ([NSThread isMainThread]) {\ 3 block();\ 4 } else {\ 5 dispatch_async(dispatch_get_main_queue(), block);\ 6 }
後面咱們看到有一處代碼,表示即便options中有SDWebImageDelayPlaceholder這一選項,也給image賦值placeholder,爲啥了?由於此時image已經從網絡端加載過了,可是網絡端獲取image沒成功,此時纔會用placeholder來替代,赤裸裸的備胎,有代碼爲證。
else { // image已經嘗試獲取過了,可是沒有從網絡端獲取到 if ((options & SDWebImageDelayPlaceholder)) { wself.image = placeholder; [wself setNeedsLayout]; } }
而else上面的else if那段代碼,就是表示image從網絡獲取成功,直接賦值給image。
哈哈,不知道大家會不會有疑惑,你怎麼知道此處表示向網絡獲取image的,也就是註釋中說的the image is loading?~~我猜的,不過我猜的沒錯的話,這段獲取的代碼既然總體賦值給了id <SDWebImageOperation> operation,那多是爲了多任務(多個圖片加載),爲何呢?我懷疑SDWebImageOperation是一個NSOperation子類(這樣才能放到NSOperationQueue中進行多任務嘛)。大家確定說我是SB,這一看就是一個protocol嘛!確實是我猜錯了,可是我隱約以爲既然叫Operation,不跟NSOperation有點關係也說不清啊,或者它可能模仿了NSOperation的多任務運行方式。以上都是猜想,咱們仍是來看代碼(後面會揭祕)。
以上的代碼(還有幾處沒說,可是涉及到什麼SDImageCacheType還有其餘的,暫時不去想)封裝成的operation做爲參數放到了sd_setImageLoadOperation中。咱們接着跳到sd_setImageLoadOperation函數中。很簡單,只有三行,我直接貼代碼了:
1 - (void)sd_setImageLoadOperation:(id)operation forKey:(NSString *)key { 2 [self sd_cancelImageLoadOperationWithKey:key]; 3 NSMutableDictionary *operationDictionary = [self operationDictionary]; 4 [operationDictionary setObject:operation forKey:key]; 5 }
雖然不少變量和函數不認識,可是咱們大概也能猜到這三行作了什麼。我先看[self operationDictionary],具體定義不要看,咱們知道它是一個NSMutableDictionary便可,並且既然叫operationDictionary,那麼存放的必定是各類operation的序列了(固然也就包括SDWebImageOperation類型的operation),並且這些operation是根據key來索引的。好的,咱們回到函數中。一進函數,先取消索引爲key的operation的操做,有些人說,若是我以前正在進行索引爲key的操做,那不就取消了嘛?是啊,就是這樣,若是該operation存在,就取消掉了,還要刪除這個key對應的object(operation)。而後從新設置key對應的operation。咱們能夠看看函數sd_cancelImageLoadOperationWithKey。(這一段文字我解釋得很差,下面評論區有詳細解釋)。
1 - (void)sd_cancelImageLoadOperationWithKey:(NSString *)key { 2 // Cancel in progress downloader from queue 3 NSMutableDictionary *operationDictionary = [self operationDictionary]; 4 id operations = [operationDictionary objectForKey:key]; 5 if (operations) { 6 if ([operations isKindOfClass:[NSArray class]]) { 7 for (id <SDWebImageOperation> operation in operations) { 8 if (operation) { 9 [operation cancel]; 10 } 11 } 12 } else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){ 13 [(id<SDWebImageOperation>) operations cancel]; 14 } 15 [operationDictionary removeObjectForKey:key]; 16 } 17 }
代碼也很容易理解,先獲取到operation的序列,即[self operationDictionary]。而後根據key來索引到對應的operation,若是operation存在的話。就要取消該operation。這裏有一個注意的地方,也是我以前沒想到的,就是索引到的operation其實一組operation的集合,那麼就須要來個遍歷一個個取消掉operation序列中的operation了。最後移除key對應的object。
這裏我有個疑惑:爲啥operation都是id<SDWebImageOperation>?並且,大家也注意到了SDWebImageOperation只有一個cancel接口。爲何要這樣設計,還有待進一步研究。
咱們仍是回到sd_setImageWithURL這個函數中,如今咱們有個大概思路了。咱們來看看咱們可以理解的部分:
未完待續,請君移步【原】SDWebImage源碼閱讀(二)。