面試時如何優雅的談論源碼
當前的大環境比去年差了很多,一方面求職的人多了而崗位因為經濟形勢的影響卻相應的少了很多。通貨膨脹,生活壓力只增不減。對于應聘的萬千大軍而言,如何能脫穎而出,是個值得考慮的技術問題。自己的價值在于不可替代性或是難以替代性。如果,隨便找個程序員就能把你replace掉,你的價值就很低廉了。如果在你負責的某個方面,只有20%的人超越你,那你的價值、你的重要性就凸顯出來了,你與雇主的關系就從被動轉向了主動,你就有了談判的籌碼。在專業化高度分工的今天,一技之長并不是說需要你掌握某個很大的方面,而只需要你能掌握其中的某一個小的領域,并不斷地深入下去。思考源碼將是一個很好的切入點。
本文主要涉及一下幾點:
- SDWebImage 原理
- SDWebImage 使用
- SDWebImage 源碼分析
- 一些思考
SDWebImage 加載圖片原理
具體源碼分析 見GIT。
目前標注的類
通過標注的類,看懂應該沒問題了。
SDWebImage是一個圖片緩存的框架。相較于AFNetworking集成的UIImageView+AFNetworking.h,對于圖片的緩存實際應用的是NSURLCache自帶的cache機制。而NSURLCache每次都要把緩存的raw data 再轉化為UIImage,就帶來了數據處理和內存方面的更多操作。SDWebImage的緩存由SDImageCache類來實現,這是一個單例類,該類負責處理內存緩存及一個可選的磁盤緩存,其中磁盤緩存的寫操作是異步的,這樣就不會對UI操作造成影響。此外還提供了若干屬性和接口來配置和操作緩存對象。包含以下功能:
1.提供UIImageView的一個分類,以支持網絡圖片的加載與緩存管理
2.一個異步的圖片加載器
3.一個異步的內存+磁盤圖片緩存
4.支持GIF圖片
5.支持WebP圖片
6.后臺圖片解壓縮處理
7.確保同一個URL的圖片不被下載多次
8.確保虛假的URL不會被反復加載
9.確保下載及緩存時,主線程不被阻塞
SDWebImage底層實現原理:
SDWebImage有沙盒緩存機制,主要由三塊組成
1.內存圖片緩存
2.內存操作緩存
3.磁盤沙盒緩存
SDWebImage的大部分工作是由緩存對象SDImageCache和異步下載器管理對象SDWebImageManager來完成的。SDWebImage的圖片下載是由SDWebImageDownloader這個類來實現的,它是一個異步下載管理器,下載過程中增加了對圖片加載做了優化的處理。而真正實現圖片下載的是自定義的一個Operation操作,將該操作加入到下載管理器的操作隊列downloadQueue中,Operation操作依賴系統提供的NSURLConnection類實現圖片的下載。
SDWebImage提供了對圖片緩存的支持,而該功能是由SDImageCache類來完成的。該類負責處理內存緩存及一個可選的磁盤緩存。內存緩存的處理是使用NSCache對象來實現的。NSCache是一個類似于集合的容器。它存儲key-value對,這一點類似于NSDictionary類,用搜索文件系統的方式做管理,文件替換方式是以時間為單位。我們通常用使用緩存來臨時存儲短時間使用但創建昂貴的對象。重用這些對象可以優化性能,因為它們的值不需要重新計算。另外一方面,這些對象對于程序來說不是緊要的,在內存緊張時會被丟棄。
磁盤緩存的處理則是使用NSFileManager對象來實現的。圖片存儲的位置是位于Cache文件夾。另外,SDImageCache還定義了一個串行隊列,來異步存儲圖片。
當SDWebImageManager向SDImageCache要資源時,先搜索內存層面的數據,如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然后做Decoder,將圖片對象放到內存層面做備份,再返回調用層。使用Decoder 是因為UIImage的imageWithData函數是每次畫圖的時候才將Data解壓成ARGB的圖像,
所以在每次畫圖的時候,會有一個解壓操作,這樣效率很低,但是只有瞬時的內存需求。
為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然后畫在另外一張圖片上,這樣這張新圖片就不再需要重復解壓了。是典型的空間換時間的做法。
SDWebImage的原理
1.使用
- (void)sd_setImageWithURL:(NSURL *)urlplaceholderImage:(UIImage *)placeholderoptions:(SDWebImageOptions)options;
會先把 placeholderImage 顯示,然后 SDWebImageManager 根據 URL 開始處理圖片。
2.進入 SDWebImageManager
-downloadWithURL:options:progress:completed:
交給 SDImageCache 從緩存查找圖片是否已經下載。
3.先從內存圖片緩存查找是否有圖片,如果內存中已經有圖片緩存,取緩存,沒有從 - (UIImage )diskImageForKey:(NSString )key 去磁盤緩存中去查找,根據 URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。在磁盤緩存中找到后,同時更新置內存緩存中(如果空閑內存過小,會先清空內存緩存),有回調則調用doneBlock回調。
4.找到了就從SDWebImageQueryCompletedBlock到 UIImageView+WebCache 等前端展示圖片。
5.如果從硬盤緩存目錄讀取不到圖片,說明不存在該圖片,需要下載圖片,共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。 圖片下載由 NSURLSession 來做,實現相關 delegate 來判斷圖片下載中、下載完成和下載失敗。
6. URLSession:didReceiveData: 中利用 ImageIO 做了按圖片下載進度加載效果。數據下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
7.圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進行二次處理,最好也在這里完成,效率會好很多。
8.在主線程 SDWebImageDownloaderCompletedBlock里處理解碼完成后的操作。回調給需要的地方展示圖片。
9.從SDWebImageDownloaderProgressBlock 回調給 SDWebImageManager 告知圖片下載信息。
10.將圖片保存到 SDImageCache 中,內存緩存和硬盤緩存同時保存。寫文件到硬盤也在以單獨 NSInvocationOperation 完成,避免拖慢主線程。
11.SDImageCache 在初始化的時候會注冊一些消息通知,在內存警告或退到后臺的時候清理內存圖片緩存,應用結束的時候清理過期圖片。
12.SDWebImage 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache方便使用。 SDWebImagePrefetcher 可以預先下載圖片,方便后續使用。
SDWebImage 使用
常用到的對象:
1、UIImageView (WebCache)類別,入口封裝,實現讀取圖片完成后的回調。
2、SDWebImageManager,對圖片進行管理的中轉站,記錄那些圖片正在讀取。
向下層讀取Cache(調用SDImageCache),或者向網絡讀取對象(調用SDWebImageDownloader) 。
實現SDImageCache和SDWebImageDownloader的回調。
3、SDImageCache,根據URL的MD5摘要對圖片進行存儲和讀取(實現存在內存中或者存在硬盤上兩種實現)
實現圖片和內存清理工作。
4、SDWebImageDownloader,根據URL向網絡讀取數據(實現部分讀取和全部讀取后再通知回調兩種方式)
5、SDWebImageDecoder,異步對圖像進行了一次解壓
使用:
[self.imageViewsd_setImageWithURL:self.imageURL
placeholderImage:nil
options:SDWebImageProgressiveDownload
progress:^(NSIntegerreceivedSize, NSIntegerexpectedSize) {
...
}
completed:^(UIImage *image, NSError *error, SDImageCacheTypecacheType, NSURL *imageURL) {
...
}];
調用setImageWithURL:方法的時候,SDWebImage自動做很多事,當你需要在某一具體時刻做事情的時候,你可以覆蓋這些方法。比如在下載某個圖片的過程中要響應一個事件,就覆蓋這個方法:
//這個方法是下載imagePath2的時候響應
SDWebImageManager *manager = [SDWebImageManagersharedManager];
[managerdownloadImageWithURL:imagePath2options:SDWebImageRetryFailedprogress:^(NSIntegerreceivedSize, NSIntegerexpectedSize) {
NSLog(@"顯示當前進度");
} completed:^(UIImage *image, NSError *error, SDImageCacheTypecacheType, BOOL finished, NSURL *imageURL) {
NSLog(@"下載完成");
}];
基本代碼:
使用SDWebImageManager類:可以進行一些異步加載的工作。
SDWebImageManager *manager = [SDWebImageManagersharedManager];
UIImage *cachedImage = [managerimageWithURL:url]; // 將需要緩存的圖片加載進來
if (cachedImage) {
// 如果Cache命中,則直接利用緩存的圖片進行有關操作
// Use the cached image immediatly
} else {
// 如果Cache沒有命中,則去下載指定網絡位置的圖片,并且給出一個委托方法
// Start an async download
[managerdownloadWithURL:urldelegate:self];
}
當然你的類要實現SDWebImageManagerDelegate協議,并且要實現協議的webImageManager:didFinishWithImage:方法。
// 當下載完成后,調用回調方法,使下載的圖片顯示
- (void)webImageManager:(SDWebImageManager *)imageManagerdidFinishWithImage:(UIImage *)image {
// Do something with the downloaded image
}
獨立的異步圖像下載
可能會單獨用到異步圖片下載,則一定要用downloaderWithURL:delegate:來建立一個SDWebImageDownloader實例。
downloader = [SDWebImageDownloaderdownloaderWithURL:urldelegate:self];
這樣SDWebImageDownloaderDelegate協議的方法imageDownloader:didFinishWithImage:被調用時下載會立即開始并完成。
獨立的異步圖像緩存
SDImageCache類提供一個創建空緩存的實例,并用方法imageForKey:來尋找當前緩存。
UIImage *myCachedImage = [[SDImageCachesharedImageCache] imageFromKey:myCacheKey];
存儲一個圖像到緩存是使用方法storeImage: forKey:
[[SDImageCachesharedImageCache] storeImage:myImageforKey:myCacheKey];
默認情況下,圖像將被存儲在內存緩存和磁盤緩存中。如果僅僅是想內存緩存中,要使用storeImage:forKey:toDisk:方法的第三個參數帶一負值
來替代。
SDWebImage 源碼分析示例
SDWebImageDownloader類
SDWebImageDownloaderOptions定義:
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1
下載順序:
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
// 以隊列的方式,按照先進先出的順序下載。這是默認的下載順序
SDWebImageDownloaderFIFOExecutionOrder,
// 以棧的方式,按照后進先出的順序下載。
SDWebImageDownloaderLIFOExecutionOrder
};
每個下載操作都定義了回調操作,如下載進度回調,下載完成回調,頭部過濾等,這些回調操作是以block形式來呈現;每個下載操作的下載進度回調和下載完成回調,這兩個回調稍后將保存在下載管理器的URLCallbacks字典中,key為URL,value為一個數組,數組里面又存放一個保存了下載進度回調和完成回調代碼塊的字典。這個字典數組同時也保證了同一張圖片只會被下載一次。
// 下載進度
typedef void(^SDWebImageDownloaderProgressBlock)(NSIntegerreceivedSize, NSIntegerexpectedSize);
// 下載完成
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
// Header過濾
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
為了保證URLCallbacks操作(添加、刪除)的線程安全性,SDWebImageDownloader將這些操作作為一個個任務放到barrierQueue隊列中,并設置屏障來確保同一時間只有一個線程操作URLCallbacks屬性。
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlockandCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlockforURL:(NSURL *)urlcreateCallback:(SDWebImageNoParamsBlock)createCallback {
...
// 1. 以dispatch_barrier_sync操作來保證同一時間只有一個線程能對URLCallbacks進行操作
dispatch_barrier_sync(self.barrierQueue, ^{
...
// 2. 處理同一URL的同步下載請求的單個下載
});
}
下載請求的管理都是放在downloadImageWithURL:options:progress:completed:方法里面來處理的,該方法調用了上面所提到的addProgressCallback:andCompletedBlock:forURL:createCallback:方法來將請求的信息存入管理器中,同時在創建回調的block中創建新的操作,配置之后將其放入downloadQueue操作隊列中,最后方法返回新創建的操作。
- (id )downloadImageWithURL:(NSURL *)urloptions:(SDWebImageDownloaderOptions)optionsprogress:(SDWebImageDownloaderProgressBlock)progressBlockcompleted:(SDWebImageDownloaderCompletedBlock)completedBlock {
...
[self addProgressCallback:progressBlockandCompletedBlock:completedBlockforURL:urlcreateCallback:^{
...
}
下載操作的超時時間可以通過downloadTimeout屬性來設置,默認值為15秒。
SDWebImage定義了一個協議,即 SDWebImageOperation 作為圖片下載操作的基礎協議。它只聲明了一個cancel方法,用于取消操作。每個圖片的下載都是一個Operation操作。SDWebImage自定義了一個Operation類,即 SDWebImageDownloaderOperation ,它繼承自NSOperation,并采用了SDWebImageOperation協議。除了繼承而來的方法,該類只向外暴露了一個方法,initWithRequest:options:progress:completed:cancelled:。對于圖片的下載,SDWebImageDownloaderOperation完全依賴于URL加載系統中的NSURLSession。具體看代碼 源碼分析 .
- (void)URLSession:(NSURLSession *)sessiondataTask:(NSURLSessionDataTask *)dataTaskdidReceiveData:(NSData *)data
方法的主要任務是接收數據。每次接收到數據時,都會用現有的數據創建一個CGImageSourceRef對象以做處理。在首次獲取到數據時(width+height==0)會從這些包含圖像信息的數據中取出圖像的長、寬、方向等信息以備使用。而后在圖片下載完成之前,會使用CGImageSourceRef對象創建一個圖片對象,經過縮放、解壓縮操作后生成一個UIImage對象供完成回調使用。當然,在這個方法中還需要處理的就是進度信息。如果我們有設置進度回調的話,就調用這個進度回調以處理當前圖片的下載進度。
縮放操作可以查看SDWebImageCompat文件中的SDScaledImageForKey函數;解壓縮操作可以查看SDWebImageDecoder文件+decodedImageWithImage方法。在下載完成或下載失敗后,需要停止當前線程的run loop,清除連接,并拋出下載停止的通知。如果下載成功,則會處理完整的圖片數據,對其進行適當的縮放與解壓縮操作,以提供給完成回調使用。
最重要是自己分析,看過的會忘,消化了才是自己的。學而不思則罔,思而不學則殆。每個人要了解自己的優缺點。有思考才有所得!
一些思考
我們應該把精力和時間投入在更值得學習的東西上。如果純看代碼而沒有碰到這個場景就算看懂了也沒法理解,學習的目的是為了實踐,而不要為了原理而分析原理,這樣就本末倒置了。對于廣大程序員而言,做碼農,通過低水平重復的勞動來創造價值的道路是永遠不可能一勞永逸的,恰恰相反,是永勞一逸的!生產只能夠惠及當下!事物的發展在于思考,可能每個人走的路徑不一樣,尋找到最適合自己的,堅持走下去。只要能肯定每天都是進步的,快慢又有什么關系呢。這是一個浮躁的社會,這個社會催生了無數可能,也許不是每個人都能做一輩子的程序員,但不要辜負你的時光。你之所以成為你,是因為你的時間,你的經歷在哪里,你就在哪里。共勉!
來自:http://ios.jobbole.com/90410/