1. SDWebImage內部實現原理步驟
1.1 實現步驟
1. 入口setImageWithUrl:placeHolderImage:options:會把placeHolderImage顯示,然後SDWebImageManager根據URL開始處理圖片.
2. 進入SDWebImageManager-downloadWithURL:delegate:options:userInfo:交給SDImageCache從快取查詢圖片是否已經下載queryDiskCacheForKey:delegate:userInfo:
3. 先從記憶體圖片快取查詢是否有圖片,如果記憶體中已經有圖片快取,SDImageCacheDelegate回撥imageCache:didFineImage:forKey:userInfo:到SDWebImageManager.
4. SDWebImageManagerDelegate回撥webImageManager:didFinishWithImage:到UIImageView + WebCache等前端展示圖片.
5. 如果記憶體快取中沒有,生成NSInvocationOperation新增到佇列開始從硬碟查詢圖片是否已經快取
6. 根據URLKey在硬碟快取目錄下嘗試讀取圖片檔案.這一步是在NSOperation進行的操作,所以回主執行緒進行結果回撥notifyDelegate.
7. 如果上一操作從硬碟讀取到了圖片,將圖片新增到記憶體快取中(如果空閒記憶體過小 會先清空記憶體快取).SDImageCacheDelegate 回撥imageCache:didFinishImage:forKey:userInfo:進而回撥展示圖片.
8. 如果從硬碟快取目錄讀取不到圖片,說明所有快取都不存在該圖片,需要下載圖片,回撥imageCache:didNotFindImageForKey:userInfo.
9. 共享或重新生成一個下載器SDWebImageDownLoader開始下載圖片
10. 圖片下載由NSURLConnection來做,實現相關delegate來判斷圖片下載中,下載完成和下載失敗
11. connection:didReceiveData:中利用ImageIO做了按圖片下載進度載入效果
12. connectionDidFinishLoading:資料下載完成後交給SDWebImageDecoder做圖片解碼處理
13. 圖片解碼處理在一個NSOperationQueue完成,不會拖慢主執行緒UI.如果有需要對下載的圖片進行二次處理,最好也在這裡完成,效率會好很多.
14. 在主執行緒notifyDelegateOnMainThreadWithInfo:宣告解碼完成imageDecoder:didFinishDecodingImage:userInfo:回撥給SDWebImageDownloader
15. imageDownLoader:didFinishWithImage:回撥給SDWebImageManager告知圖片下載完成
16. 通知所有的downloadDelegates下載完成,回撥給需要的地方展示圖片
17. 將圖片儲存到SDImageCache中記憶體快取和硬碟快取同時儲存,寫檔案到硬碟也在以單獨NSInvocationOperation完成,避免拖慢主執行緒
18. SDImageCache在初始化的時候會註冊一些訊息通知,在記憶體警告或退到後臺的時候清理記憶體圖片快取,應用結束的時候清理過期圖片
19. SDWI也提供UIButton + WebCache和MKAnnptation + WebCache方便使用
20. SDWebImagePrefetcher 可以預先下載圖片,方便後續使用
複製程式碼
再用一張圖說明:
1.2 API中引數列舉型別
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];
}
複製程式碼
其實,以上這些API都直接或間接利用到了SDWebImageOptions這個引數,那麼你記得這個引數有哪些型別?各有什麼作用?
通過檢視SDWebImageManager.h
原始碼,可知如下:
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
// 預設情況下,當URL下載失敗時,URL會被列入黑名單,導致庫不會再去重試,該標記用於禁用黑名單
SDWebImageRetryFailed = 1 << 0,
// 預設情況下,圖片下載開始於UI互動,該標記禁用這一特性,這樣下載延遲到UIScrollView減速時
SDWebImageLowPriority = 1 << 1,
// 該標記禁用磁碟快取
SDWebImageCacheMemoryOnly = 1 << 2,
// 該標記啟用漸進式下載,圖片在下載過程中是漸漸顯示的,如同瀏覽器一下。
// 預設情況下,影象在下載完成後一次性顯示
SDWebImageProgressiveDownload = 1 << 3,
// 即使圖片快取了,也期望HTTP響應cache control,並在需要的情況下從遠端重新整理圖片。
// 磁碟快取將被NSURLCache處理而不是SDWebImage,因為SDWebImage會導致輕微的效能下載。
// 該標記幫助處理在相同請求URL後面改變的圖片。如果快取圖片被重新整理,則完成block會使用快取圖片呼叫一次
// 然後再用最終圖片呼叫一次
SDWebImageRefreshCached = 1 << 4,
// 在iOS 4+系統中,當程式進入後臺後繼續下載圖片。這將要求系統給予額外的時間讓請求完成
// 如果後臺任務超時,則操作被取消
SDWebImageContinueInBackground = 1 << 5,
// 通過設定NSMutableURLRequest.HTTPShouldHandleCookies = YES;來處理儲存在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
// 允許不受信任的SSL認證
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
// 預設情況下,圖片下載按入隊的順序來執行。該標記將其移到佇列的前面,
// 以便圖片能立即下載而不是等到當前佇列被載入
SDWebImageHighPriority = 1 << 8,
// 預設情況下,佔點陣圖片在載入圖片的同時被載入。該標記延遲佔點陣圖片的載入直到圖片已以被載入完成
SDWebImageDelayPlaceholder = 1 << 9,
// 通常我們不呼叫動畫圖片的transformDownloadedImage代理方法,因為大多數轉換程式碼可以管理它。
// 使用這個票房則不任何情況下都進行轉換。
SDWebImageTransformAnimatedImage = 1 << 10,
};
複製程式碼
1.2.2 SDImageCacheType:圖片快取策略
例如,設定圖片的兩個例子
[self.image2 sd_setImageWithURL:imagePath2 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"這裡可以在圖片載入完成之後做些事情");
}];
複製程式碼
//使用預設圖片,而且用block 在完成後做一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"圖片載入完成後做的事情");
}];
複製程式碼
還有獲取下載進度的例子
//使用預設圖片,而且用block 在完成後做一些事情
[self.image1 sd_setImageWithURL:imagePath1 placeholderImage:[UIImage imageNamed:@"default"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
NSLog(@"圖片載入完成後做的事情");
}];
複製程式碼
上面的例子,都有個SDImageCacheTyp
的引數,你記得這個引數有哪些?
- SDImageCacheType
//定義Cache型別
typedef NS_ENUM(NSInteger, SDImageCacheType) {
//不使用cache獲得圖片,依然會從web下載圖片
SDImageCacheTypeNone,
//圖片從disk獲得
SDImageCacheTypeDisk,
//圖片從Memory中獲得
SDImageCacheTypeMemory
};
複製程式碼
2. 最大快取和時間設定
- SDImageCache類的原始碼
//這個變數預設值為YES,顯示比較高質量的圖片,但是會浪費比較多的記憶體,可以通過設定NO來緩解記憶體
@property (assign, nonatomic) BOOL shouldDecompressImages;
//總共的記憶體允許圖片的消耗值
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//圖片存活於記憶體的時間初始化的時候預設為一週
@property (assign, nonatomic) NSInteger maxCacheAge;
//每次儲存圖片大小的限制
@property (assign, nonatomic) NSUInteger maxCacheSize;
複製程式碼
- 設定maxCacheSize的例子
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager.imageCache setMaxMemoryCost:1000000];//設定總快取大小,預設為0沒有限制
[manager.imageCache setMaxCacheSize:640000];//設定單個圖片限制大小
[manager.imageDownloader setMaxConcurrentDownloads:1];//設定同時下載執行緒數,這是下載器的內容,下面將會介紹
[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(兩張測試圖片)
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. 區分:三種種快取(記憶體圖片快取,磁碟圖片快取,記憶體操作快取)
- 先檢視記憶體圖片快取,記憶體圖片快取沒有,後生成操作,檢視磁碟圖片快取
- 磁碟圖片快取有,就載入到記憶體快取,沒有就下載圖片
- 在建立下載操作之前,判斷下載操作是否存在
- 預設情況下,下載的圖片資料會同時快取到記憶體和磁碟中
關於快取位置
- 記憶體快取是通過 NSCache的子類AutoPurgeCache來實現的;
- 磁碟快取是通過 NSFileManager 來實現檔案的儲存(預設路徑為/Library/Caches/default/com.hackemist.SDWebImageCache.default),是非同步實現的。
關於圖片下載操作
SDWebImage的大部分工作是由快取物件SDImageCache和非同步下載器管理物件SDWebImageManager來完成的。
SDWebImage的圖片下載是由SDWebImageDownloader這個類來實現的,它是一個非同步下載管理器,下載過程中增加了對圖片載入做了優化的處理。而真正實現圖片下載的是自定義的一個Operation操作,將該操作加入到下載管理器的操作佇列downloadQueue中,Operation操作依賴系統提供的NSURLConnection類實現圖片的下載。
4. 高清和低清圖片與網路環境的問題
- 網路判斷的問題--利用AFNetworking的API 首先,啟用監控
// AppDelegate.m 檔案中
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// 監控網路狀態
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
}
複製程式碼
然後,在需要的地方獲取監控管理
// 以下程式碼在需要監聽網路狀態的方法中使用
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下載原圖
} else { // 其他,下載小圖
}
複製程式碼
-
並不能簡單的這樣:WIFI就下載高清圖,蜂窩網路就下載縮圖。要考慮和利用快取的因素。
-
一個典型例子
- setItem:(CustomItem *)item
{
_item = item;
// 佔點陣圖片
UIImage *placeholder = [UIImage imageNamed:@"placeholderImage"];
// 從記憶體\沙盒快取中獲得原圖,
UIImage *originalImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.originalImage];
if (originalImage) { // 如果記憶體\沙盒快取有原圖,那麼就直接顯示原圖(不管現在是什麼網路狀態)
self.imageView.image = originalImage;
} else { // 記憶體\沙盒快取沒有原圖
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
if (mgr.isReachableViaWiFi) { // 在使用Wifi, 下載原圖
[self.imageView sd_setImageWithURL:[NSURL URLWithString:item.originalImage] placeholderImage:placeholder];
} else if (mgr.isReachableViaWWAN) { // 在使用手機自帶網路
// 使用者的配置項假設利用NSUserDefaults儲存到了沙盒中
// [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"alwaysDownloadOriginalImage"];
// [[NSUserDefaults standardUserDefaults] synchronize];
#warning 從沙盒中讀取使用者的配置項:在3G\4G環境是否仍然下載原圖
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 { // 沒有網路
UIImage *thumbnailImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:item.thumbnailImage];
if (thumbnailImage) { // 記憶體\沙盒快取中有小圖
self.imageView.image = thumbnailImage;
} else { // 處理離線狀態,而且有沒有快取時的情況
self.imageView.image = placeholder;
}
}
}
}
複製程式碼
5. 可能的問題
5.1 後臺沒有處理的高清大圖導致APP記憶體過大而奔潰?
- 思路,改寫sd_imageWithData方法的原始碼,可參考https://blog.csdn.net/benyoulai5/article/details/50462586
5.2 SD怎樣解決Cell移出螢幕後的cell中的UIImageView繼續下載問題?-- 移除UIImageView當前繫結的操作。
- SD為設定UIImageView提供的API,歸根結底呼叫的是下面API:
- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {
複製程式碼
- 它的方法實現體中,第一句話就是
[self sd_cancelCurrentImageLoad];
- 這個非常關鍵,當在TableView的cell包含了的UIImageView被重用時,首先呼叫這一行程式碼,保證這個ImageView的下載和快取組合操作都被取消。如果: ①上次賦值的圖片正在下載,則下載不再進行; ②下載完成了,但還沒有執行到呼叫回撥(回撥包含wself.image = image),由於操作被取消,因而不會顯示和重用的cell相同的圖片; ③以上兩種情況只有在網速極慢和手機處理速度極慢的情況下才會發生,實際上發生的概率非常小,大多數是這種情況:操作已經進行到下載完成了,這次使用的cell是一個重用的cell,而且保留著imageView的image,對於這種情況SD會在該實現方法裡面接著設定佔點陣圖的語句,將image暫時設定為佔點陣圖,如果佔點陣圖為空,就意味著先暫時清空image。
- SD內部移除繫結的操作的呼叫棧為:
- (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怎麼實現螢幕滑動時,暫停資料來源的任務?通過NSOperationQueue的setSuspend
嗎?
3.1 基於NSURLConnection的SDWebImage
(至少2014年7月的版本)老版本的基於 NSURLConnection 的 SDWebImage 是通過這樣的機制:NSURLConnection工作在主執行緒,雖然NSURLConnection工作在子執行緒,但因為UI相關的操作和回撥中的setImage
都在同一個主執行緒,滑動螢幕會導致主執行緒的runloop切換mode為UITrackingRunLoopMode
,此時原來kCFRunLoopDefaultMode
上的source (這裡是NSURLConnectionsetImage
) 都被暫停,runloop執行不到回撥。
它的本意是不讓網路相關的操作阻塞到主執行緒,改正:網路相關的操作在子執行緒,主執行緒runloop的mode切換並不會影響子執行緒,但是它這樣設計的確有這樣的效果:螢幕滑動時,暫停資料下載的任務,改正:滑動螢幕並不會暫停資料下載,暫停的是同一個主執行緒的setImage
。
在老版本中,在SDWebImageDownloaderOperation.m檔案中有這樣一段話:
3.2 基於NSURLSession的SDWebImage
然而,新版本的 SDWebImage 是基於 NSURLSession 的,這個NSURLSession不同於NSURLConnection的最大區別是不是基於主執行緒 子執行緒 的runloop控制的,而是通過NSOperation新開子執行緒,所以同意主執行緒的runloop切換mode並不會影響子執行緒的操作。所以,新版本的SDWebImage是沒有這個“滑動即暫停”的效果的。改正:同樣,滑動螢幕並不會暫停資料下載,暫停的是同一個主執行緒的setImage
。
如果,實在有需要,有兩種辦法,可以自己改寫setImage的方法,在裡面設定工作的mode,同老版的SDWebImage一樣改正:一種是改變setImage的執行緒或者mode。還有一種辦法,可以監聽ScrollView的拉拽狀態,當ScrollView的代理方法監聽到被拉拽,就suspend操作。