SDWebImage :github托管地址https://github.com/rs/SDWebImage
一刑枝、功能簡介:
一個添加了web圖片加載和緩存管理的UIImageView分類
一個異步圖片下載器
一個異步的內(nèi)存加磁盤綜合存儲圖片并且自動處理過期圖片
支持動態(tài)gif圖
支持webP格式的圖片
后臺圖片解壓處理
確保同樣的圖片url不會下載多次
確保偽造的圖片url不會重復(fù)嘗試下載
確保主線程不會阻塞
二豺憔、SDWebImage 加載圖片的流程
入口
setImageWithURL:placeholderImage:options:
會先把placeholderImage
顯示,然后SDWebImageManager
根據(jù) URL 開始處理圖片-
進(jìn)入
SDWebImageManager-downloadWithURL:delegate:options:userInfo:
霜浴,交給SDImageCache
從緩存查找圖片是否已經(jīng)下載queryDiskCacheForKey:delegate:userInfo:
- 先從內(nèi)存圖片緩存查找沮趣,如果內(nèi)存中已經(jīng)有圖片緩存,
SDImageCacheDelegate
回調(diào)imageCache:didFindImage:forKey:userInfo:
到SDWebImageManager
-
SDWebImageManagerDelegate
回調(diào)webImageManager:didFinishWithImage:
到 UIImageView+WebCache 等前端展示圖片
- 先從內(nèi)存圖片緩存查找沮趣,如果內(nèi)存中已經(jīng)有圖片緩存,
-
如果內(nèi)存緩存中沒有坷随,生成
NSInvocationOperation
添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存- 根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件房铭。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào)
notifyDelegate:
- 如果上一操作從硬盤讀取到了圖片温眉,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小缸匪,會先清空內(nèi)存緩存)。
SDImageCacheDelegate
回調(diào)imageCache:didFindImage:forKey:userInfo:
类溢;進(jìn)而回調(diào)展示圖片
- 根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件房铭。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào)
如果從硬盤緩存目錄讀取不到圖片凌蔬,說明所有緩存都不存在該圖片,需要下載圖片闯冷,回調(diào)
imageCache:didNotFindImageForKey:userInfo:
共享或重新生成一個下載器
SDWebImageDownloader
開始下載圖片-
connectionDidFinishLoading:
數(shù)據(jù)下載完成后交給SDWebImageDecoder
做圖片解碼處理- 圖片解碼處理在一個
NSOperationQueue
完成砂心,不會拖慢主線程 UI。如果有需要對下載的圖片進(jìn)行二次處理蛇耀,可以在這里完成辩诞,效率會好很多。 - 在主線程
notifyDelegateOnMainThreadWithInfo:
宣告解碼完成纺涤,imageDecoder:didFinishDecodingImage:userInfo:
回調(diào)給 SDWebImageDownloader
- 圖片解碼處理在一個
imageDownloader:didFinishWithImage:
回調(diào)給 SDWebImageManager 告知圖片下載完成通知所有的 downloadDelegates 下載完成译暂,回調(diào)給需要的地方展示圖片
將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時保存撩炊。寫文件到硬盤也在以單獨(dú) NSInvocationOperation 完成外永,避免拖慢主線程
三、圖片緩存策略: (不緩存拧咳,內(nèi)存緩存伯顶,沙盒緩存)
-
SDImageCache是怎么做數(shù)據(jù)管理的?
SDImageCache分兩個部分,一個是內(nèi)存層面的祭衩,一個是硬盤層面的灶体。
內(nèi)存層面的相當(dāng)是個緩存器,以Key-Value的形式存儲圖片汪厨。當(dāng)內(nèi)存不夠的時候會清除所有緩存圖片。
用搜索文件系統(tǒng)的方式做管理愉择,文件替換方式是以時間為單位劫乱,剔除時間大于一周的圖片文件
當(dāng)SDWebImageManager向SDImageCache要資源時,先搜索內(nèi)存層面的數(shù)據(jù)锥涕,如果有直接返回衷戈,沒有的話去訪問磁盤,將圖片從磁盤讀取出來层坠,然后做Decoder殖妇,將圖片對象放到內(nèi)存層面做備份,再返回調(diào)用層破花。
-
具體代碼:
- Memory Cache:
@interface SDImageCache () #pragma mark - Properties @property (strong, nonatomic, nonnull) NSCache *memCache; // 這里我們發(fā)現(xiàn)谦趣, 有一個叫做 memCache 的屬性,它是一個 NSCache 對象座每,用于實(shí)現(xiàn)我們對圖片的 Memory Cache前鹅。 // NSCache 簡單來說,它是一個類似于 NSDictionary 的集合類峭梳,用于在內(nèi)存中存儲我們要緩存的數(shù)據(jù)舰绘。詳細(xì)信息大家可以參考官方文檔:https://developer.apple.com/reference/foundation/nscache
// SDWebImage 還專門實(shí)現(xiàn)了一個叫做 AutoPurgeCache 的類, 相比于普通的 NSCache葱椭, 它提供了一個在內(nèi)存緊張時候釋放緩存的能力: // 接受系統(tǒng)的內(nèi)存警告通知捂寿,然后清除掉自身的圖片緩存 @interface AutoPurgeCache : NSCache @end @implementation AutoPurgeCache - (nonnull instancetype)init { self = [super init]; if (self) { #if SD_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; #endif } return self; }
- Disk Cache:
也就是文件緩存,SDWebImage 會將圖片存放到 NSCachesDirectory 目錄中孵运,然后為每一個緩存文件生成一個 md5 文件名, 存放到文件中秦陋。
-
Disk 緩存清理策略
SDWebImage 會在每次 APP 結(jié)束和程序切到后臺的時候執(zhí)行清理任務(wù)。 清理緩存的規(guī)則分兩步進(jìn)行治笨。 第一步先清除掉過期的緩存文件踱侣。 如果清除掉過期的緩存之后,空間還不夠大磺。 那么就繼續(xù)按文件時間從早到晚排序抡句,先清除最早的緩存文件,直到剩余空間達(dá)到要求杠愧。
-
具體點(diǎn)待榔,SDWebImage 是怎么控制哪些緩存過期,以及剩余空間多少才夠呢? 通過兩個屬性:
@interface SDImageCache : NSObject@property (assign, nonatomic) NSInteger maxCacheAge;//文件緩存的時長 @property (assign, nonatomic) NSUInteger maxCacheSize;//允許的最大緩存空間 // maxCacheAge 的默認(rèn)值 static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week // maxCacheSize 的默認(rèn)值 [SDImageCache sharedImageCache].maxCacheSize = 1024 * 1024 * 50; // 50M
四锐锣、圖片Decoder
-
為啥必須做Decoder?
-
首先來了解下幾種圖片格式的區(qū)別(png, jpg, gif ,webp,bitMap等等):
-
jpeg :
- 有損壓縮格式, 將像素信息用jpeg保存成文件再讀取出來腌闯,其中某些像素值會有少許變化。
- 沒有透明信息
- jpeg比較適合用來存儲相機(jī)拍攝出來的圖片
-
png :
- 是一種無損壓縮格式:因?yàn)樗鼈兪褂玫?(主要是基于游程長度編碼的) 壓縮算法可以減少存儲需求雕憔。這種壓縮是無損的姿骏,這意味著圖像質(zhì)量不會被壓縮過程影響
- 可以有透明效果
- 比較適合矢量圖,幾何圖
(矢量格式的一大優(yōu)點(diǎn)就是縮放.矢量格式的圖像其實(shí)是一組繪圖命令.這些指令通常是獨(dú)立于尺寸的.如果你想要擴(kuò)大一個圓形,只需在繪制之前擴(kuò)大他的半徑就可以了)
-
bitMap(位圖):
- bmp格式?jīng)]有壓縮像素格式
- 存儲在文件中時先有文件頭、再圖像頭斤彼、后面就都是像素?cái)?shù)據(jù)了分瘦,上下顛倒存儲
- 最早接觸到bitMap是在imageView的layer.shouldRasterize
-
-
CALayer的shouldRasterize和離屏渲染(offscreen rendering):
離屏渲染消耗性能原因是顯卡需要另外alloc一塊內(nèi)存來進(jìn)行渲染,渲染完畢后在繪制到當(dāng)前屏幕,而且對于顯卡來說,onscreen到offscreen的上下文環(huán)境切換是非常昂貴的(涉及到OpenGL的pipelines和barrier等).
-
開啟shouldRasterize后,CALayer會被光柵化為bitmap,layer的陰影等效果也會被保存到bitmap中.
更新已光柵化的layer,會造成大量的offscreen渲染.
當(dāng)你設(shè)置圖片的圓角屬性的時候,會觸發(fā)GPU的離屏渲染琉苇,這個過程消耗性能嘲玫。當(dāng)設(shè)置這個屬性( shouldRasterize )后為YES后 the layer is rendered as a bitmap in its local coordinate space and then composited to the destination with any other content. rendered as a bitmap這個過程是由CPU完成的,等下次使用時不會再重新去渲染了并扇,直接使用bitmap去团。如果不設(shè)置這個屬性為YES,the layer is composited directly into the destination whenever possible.造成離屏渲染
-
圖片的在內(nèi)存中的大小
圖片在內(nèi)存中占用的大小 跟圖片自身的大小沒有關(guān)系
內(nèi)存中占用的大小 = 圖片的寬度 * 圖片的高度*每個像素占用的字節(jié)數(shù)
-
在SDWebImage中衡量大圖的標(biāo)準(zhǔn):圖片的像素總數(shù) > 60M內(nèi)存所存儲的像素?cái)?shù) ? 壓縮 : 不壓縮
這個里面的大圖處理邏輯個SDWebImage 是完全一樣的,介紹更詳細(xì),建議看蘋果官方的
基本原理:也就是原圖按照定好的大小(像素).來對原圖進(jìn)行切塊,然后再一塊塊的繪制到destContext
SDWebImage部份可以參考:
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image
函數(shù)
png,jpeg格式的數(shù)據(jù)是不能直接使用的,需要將其轉(zhuǎn)化為位圖
-
當(dāng)我們使用imageView顯示圖片的時候:
- 讀取圖片
- 解壓圖片為位圖(消耗CPU)
- 如果位圖數(shù)據(jù)不是字節(jié)對齊的穷蛹,CoreAnimation會copy一份位圖數(shù)據(jù)并進(jìn)行字節(jié)對齊
- CoreAnimation渲染解壓縮過的位圖
- 這一切在IOS中都是默認(rèn)發(fā)生在主線程成的并且是在
UIImageView
執(zhí)行setImage
方法的時候完成的
-
補(bǔ)充點(diǎn):
-
+(nullableUIImage*)imageNamed:(NSString*)name
:不適合加載大的 不常用的圖片.因?yàn)樗鼤J(rèn)在程序里保存這張圖片數(shù)據(jù)(不會隨ImageView的移除而移除).只有經(jīng)常使用圖片適合這種方式加載. -
+ (nullableUIImage*)imageWithContentsOfFile:(NSString*)path
:這個方法跟上面的略有不同,他不會在內(nèi)存中保留一份數(shù)據(jù).只要imageView移除,內(nèi)存中的數(shù)據(jù)就會直接移除.這也就是這個方法為什么適合加載大的圖片,但卻不常用的圖片. - 相關(guān)的參考文章:IOS異步圖片加載與常用的優(yōu)化
-
-
-
解碼部份的代碼:
// 用來說明每個像素占用內(nèi)存多少個字節(jié)土陪,在這里是占用4個字節(jié) static const size_t kBytesPerPixel = 4; /** 表示每一個組件占多少位 比方說RGBA,其中R(紅色)G(綠色)B(藍(lán)色)A(透明度)是4個組件肴熏,每個像素由這4個組件組成旺坠,那么我們就用8位來表示著每一個組件,所以這個RGBA就是8*4 = 32位扮超。 */ static const size_t kBitsPerComponent = 8; /** 知道了kBitsPerComponent和每個像素有多少組件組成就能計(jì)算kBytesPerPixel了取刃。計(jì)算公式是:(bitsPerComponent * number of components + 7)/8. */ + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image { /** 判斷要不要解碼 并不是所有的image都要解碼的〕鏊ⅲ可以來看看shouldDecodeImage:這個函數(shù): 不適合解碼的條件為: 1. image為nil 2. animated images 動圖不適合 3. 帶有透明因素的圖像不適合 */ if (![UIImage shouldDecodeImage:image]) { return image; } // autorelease the bitmap context and all vars to help system to free memory when there are memory warning. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory]; @autoreleasepool{ CGImageRef imageRef = image.CGImage; // 顏色空間 CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef]; size_t width = CGImageGetWidth(imageRef); size_t height = CGImageGetHeight(imageRef); // 計(jì)算出每行的像素?cái)?shù) size_t bytesPerRow = kBytesPerPixel * width; // kCGImageAlphaNone is not supported in CGBitmapContextCreate. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast // to create bitmap graphics contexts without alpha info. // 創(chuàng)建沒有透明因素的bitmap graphics contexts CGContextRef context = CGBitmapContextCreate(NULL, width, height, kBitsPerComponent, bytesPerRow, colorspaceRef, kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast); if (context == NULL) { return image; } // Draw the image into the context and retrieve the new bitmap image without alpha // 繪制圖像 CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context); UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha scale:image.scale orientation:image.imageOrientation]; CGContextRelease(context); CGImageRelease(imageRefWithoutAlpha); return imageWithoutAlpha; } }
-
總結(jié):結(jié)合上面五點(diǎn)總結(jié)下為什么要解碼:
其實(shí)不解碼也是可以使用的璧疗,假如說我們通過
imageNamed:
來加載image,系統(tǒng)默認(rèn)會在主線程立即進(jìn)行圖片的解碼工作馁龟。這一過程就是把image解碼成可供控件直接使用的位圖崩侠。當(dāng)在主線程調(diào)用了大量的
imageNamed:
方法后,就會產(chǎn)生卡頓了坷檩。為了解決這個問題我們有兩種比較簡單的處理方法:- 我們不使用
imageNamed:
加載圖片却音,使用其他的方法,比如imageWithContentsOfFile:
- 我們自己解碼圖片矢炼,可以把這個解碼過程放到子線程
好處:
把圖片解碼這個默認(rèn)在主線程執(zhí)行,耗損CPU的行為,放在了后臺線程.
只需要在使用的時候,直接setImage,不會有太大的CPU消耗
弊端:
- 這樣解碼就是以空間換時間的方法,提前解壓好,用的時候直接從內(nèi)存讀取.
- 如果下載的圖片比較大,然后直接解碼的話 這個是內(nèi)存所不能承受,需要對圖片進(jìn)行壓縮.
- 我們不使用
五系瓢、部份API說明:
-
所有類的作用介紹一下(3.8.1版本)
SDImageCache //緩存 定義了 Disk 和 memory二級緩存(NSCache)負(fù)責(zé)管理cache 單例 SDWebImageManager //核心管理類 主要對緩存管理 + 下載管理進(jìn)行了封裝 主要接口downloadImageWithURL單利 SDWebImageDownloader //異步圖片下載管理,管理下載隊(duì)列句灌,管理operation 管理網(wǎng)絡(luò)請求 處理結(jié)果和異常夷陋,單例 SDWebImageCompat //保證不同平臺/版本/屏幕等兼容性的宏定義和內(nèi)聯(lián) 圖片縮放 SDWebImageDecoder //圖片解壓縮欠拾,內(nèi)部只有一個接口 存放網(wǎng)絡(luò)請求回調(diào)的block //自己理解的數(shù)據(jù)結(jié)構(gòu)大概是 // 結(jié)構(gòu){"url":[{"progress":"progressBlock"},{"complete":"completeBlock"}]} SDWebImageDownloaderOperation //實(shí)現(xiàn)了異步下載圖片的NSOperation,網(wǎng)絡(luò)請求給予NSURLSession 代理下載,自定義的Operation任務(wù)對象骗绕,需要手動實(shí)現(xiàn)start cancel等方法 SDWebImageOperation //operation協(xié)議 只定義了cancel operation這一接口 上面的downloaderOperation的代理 SDWebImagePrefetcher //低優(yōu)先級情況下預(yù)先下載圖片藐窄,對SDWebImageViewManager進(jìn)行簡單封裝 很少用 MKAnnotationView+WebCache //為MKAnnotationView異步加載圖片 NSData+ImageContentType //通過Image data判斷當(dāng)前圖片的格式 UIButton+WebCache //為UIButton異步加載圖片 UIImage+GIF //將Image data轉(zhuǎn)換成指定格式圖片 UIImage+MultiFormat //將image data轉(zhuǎn)換成指定格式圖片 UIImageView+HighlightedWebCache //為UIImageView異步加載圖片 UIImageView+WebCache //為UIImageView異步加載圖片 UIView+WebCacheOperation //保存當(dāng)前MKAnnotationView / UIButton / UIImageView異步下載圖片的operations
- SDWebImageOptions 所有選項(xiàng)
//默認(rèn)情況下,當(dāng)一個url下載失敗酬土,如果URL在黑名單中那么SDWebImage庫不進(jìn)行重試荆忍。這個標(biāo)志使黑名單失效。
SDWebImageRetryFailed = 1 << 0,
//默認(rèn)情況下撤缴,在UI交互中開始圖片下載刹枉,這個標(biāo)志使這個特性失效,例如導(dǎo)致在UIScrollView減速中延遲下載
SDWebImageLowPriority = 1 << 1,
//只進(jìn)行內(nèi)存緩存,這個標(biāo)志使硬盤緩存失效
SDWebImageCacheMemoryOnly = 1 << 2,
//這個標(biāo)志可以漸進(jìn)式下載,顯示的圖像是逐步在下載
SDWebImageProgressiveDownload = 1 << 3,
//刷新緩存
SDWebImageRefreshCached = 1 << 4,
//后臺下載腹泌,如果后臺任務(wù)時間過期那么操作將會被取消
SDWebImageContinueInBackground = 1 << 5,
//通過設(shè)置NSMutableURLRequest來操作cookies保存到NSHTTPCookieStore
//NSMutableURLRequest.HTTPShouldHandleCookies = YES;
SDWebImageHandleCookies = 1 << 6,
//允許使用無效的SSL證書,測試目的是有效的嘶卧。在生產(chǎn)環(huán)境被警告
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默認(rèn)情況下尔觉,圖片在隊(duì)列中排隊(duì)下載凉袱。這個標(biāo)志移動他們到前面的隊(duì)列中
SDWebImageHighPriority = 1 << 8,
//延遲占位符
SDWebImageDelayPlaceholder = 1 << 9,
//我們通常在動畫圖片中不調(diào)用transformDownloadedImage代理,大部分的變形代碼將損壞圖片侦铜。使用這個標(biāo)志在任何情況下變形圖片
SDWebImageTransformAnimatedImage = 1 << 10
//默認(rèn)情況下专甩,圖片是在下載完成后加載到圖片視圖;使用這個標(biāo)志钉稍,如果你想在下載成功后在完成塊中手動設(shè)置圖片涤躲。
SDWebImageAvoidAutoSetImage = 1 << 11,
// 默認(rèn)情況下,圖片解碼為原始的大小贡未。在iOS种樱,這個標(biāo)志會把圖片縮小到與設(shè)備的受限內(nèi)容相兼容的大小。如果設(shè)置了SDWebImageProgressDownload標(biāo)志俊卤,那么縮小被設(shè)置為無效嫩挤。
SDWebImageScaleDownLargeImages = 1 << 12,
// 默認(rèn)情況下,當(dāng)圖像緩存在內(nèi)存中時消恍,我們不查詢磁盤數(shù)據(jù)岂昭。 此掩碼可以強(qiáng)制同時查詢磁盤數(shù)據(jù)。
// *建議將此標(biāo)志與`SDWebImageQueryDiskSync`一起使用狠怨,以確保圖像在同一個runloop中加載约啊。
SDWebImageQueryDataWhenInMemory = 1 << 13,
// 默認(rèn)情況下,我們同步查詢內(nèi)存緩存佣赖,異步查詢磁盤緩存恰矩。 此掩碼可以強(qiáng)制同步查詢磁盤緩存,以確保在同一個runloop中加載映像憎蛤。
// *如果禁用內(nèi)存緩存或在某些其他情況下枢里,此標(biāo)志可以避免在單元重用期間閃爍。
SDWebImageQueryDiskSync = 1 << 14,
// 默認(rèn)情況下,當(dāng)緩存丟失時栏豺,將從網(wǎng)絡(luò)下載映像彬碱。 此標(biāo)志可以阻止網(wǎng)絡(luò)僅從緩存加載。
SDWebImageFromCacheOnly = 1 << 15,
// 默認(rèn)情況下奥洼,在圖像加載完成后使用“SDWebImageTransition”進(jìn)行某些視圖轉(zhuǎn)換時巷疼,此轉(zhuǎn)換僅適用于從網(wǎng)絡(luò)下載圖像。 此掩碼也可以強(qiáng)制為內(nèi)存和磁盤緩存應(yīng)用視圖轉(zhuǎn)換灵奖。
SDWebImageForceTransition = 1 << 16