SDWebImage相信大家都很熟悉,畢竟大部分項目都會用凶掰。
SDWebImage特征
- 提供了UIImageView把敢,UIButton寄摆,MKAnnotationView的category來加載網(wǎng)絡(luò)圖片并且對網(wǎng)絡(luò)圖片進(jìn)行緩存管理
- 異步下載圖片
- 異步內(nèi)存+磁盤緩存谅辣,自動緩存過期處理
- 背景圖壓縮
- 保證同一個URL不會重復(fù)幾次
- 保證失效的URL不會一次又一次地重試
- 保證主線程不會被阻塞
- 使用GCD和ARC
SDWebImage結(jié)構(gòu)
SDWebImage使用
#import "UIImageView+WebCache.h"
/*
不使用占位圖
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"]];
/*
使用占位圖
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
/*
使用占位圖修赞,并且可以自己選擇SDWebImageOptions:
//失敗重試
SDWebImageRetryFailed = 1 << 0,
//默認(rèn)情況下,圖片會在交互發(fā)生的時候下載(例如你滑動tableview的時候),這個會禁止這個特性,導(dǎo)致的結(jié)果就是在scrollview減速的時候才會開始下載
SDWebImageLowPriority = 1 << 1,
//僅支持內(nèi)存緩存
SDWebImageCacheMemoryOnly = 1 << 2,
//邊下載邊顯示
SDWebImageProgressiveDownload = 1 << 3,
//刷新緩存(更換用戶頭像)
SDWebImageRefreshCached = 1 << 4,
//啟動后臺下載
SDWebImageContinueInBackground = 1 << 5,
//通過設(shè)置處理存儲在NSHTTPCookieStore中的cookie
SDWebImageHandleCookies = 1 << 6,
//允許不安全的SSL證書
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//默認(rèn)情況下,image在加載的時候是按照他們在隊列中的順序加載的,這個會把他們移動到隊列的前端,并且是立刻加載
SDWebImageHighPriority = 1 << 8,
//默認(rèn)情況下,占位圖會在圖片下載的時候顯示.這個開啟會延遲占位圖顯示的時間,等到圖片下載完成之后才顯示占位圖
SDWebImageDelayPlaceholder = 1 << 9,
//我們通常不會在動畫圖像上調(diào)用transformDownloadedImage委托方法桑阶,因為大多數(shù)轉(zhuǎn)換代碼會對它進(jìn)行轉(zhuǎn)換柏副,使用這個來轉(zhuǎn)換它們
SDWebImageTransformAnimatedImage = 1 << 10,
//下載完成后手動設(shè)置圖片,默認(rèn)是下載完成后自動設(shè)置
SDWebImageAvoidAutoSetImage = 1 << 11,
//默認(rèn)情況下蚣录,圖像將根據(jù)其原始大小進(jìn)行解碼割择。 在iOS上,此標(biāo)志會將圖片縮小到與設(shè)備的受限內(nèi)存兼容的大小萎河。如果設(shè)置該標(biāo)志荔泳,則會關(guān)閉縮小比例
SDWebImageScaleDownLargeImages = 1 << 12
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] options:SDWebImageRefreshCached];
/**
可看到下載結(jié)果,block返回四個參數(shù)
@param image 下載后的圖片
@param error 錯誤
@param cacheType 緩存類型
@param imageURL 下載圖片URL
*/
[imageView sd_setImageWithURL:[NSURL URLWithString:@"https://"] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
/*
SDImageCacheType類型:
//沒有緩存虐杯,直接下載
SDImageCacheTypeNone,
//從磁盤緩存中獲得
SDImageCacheTypeDisk,
//從內(nèi)存緩存中獲得
SDImageCacheTypeMemory
*/
}];
這就是基本的使用方法了玛歌,很簡單,順便看下內(nèi)存緩存和磁盤緩存區(qū)別:
- 內(nèi)存是指當(dāng)前程序的運(yùn)行空間擎椰,可用來臨時存儲文件支子,磁盤是指當(dāng)前程序的存儲空間,可用來永久存儲文件达舒。
- 內(nèi)存緩存速度快容量小值朋,磁盤緩存容量大速度慢可持久化。
iOS采用沙盒機(jī)制巩搏,每個應(yīng)用程序只能在該程序創(chuàng)建的文件系統(tǒng)中讀取文件昨登,不可以去其它地方訪問,此區(qū)域被稱為沙盒贯底。默認(rèn)情況下丰辣,每個沙盒包含3個文件夾:Documents, Library 和tmp。
- Documents:建議將所有的應(yīng)用程序數(shù)據(jù)文件寫入到這個目錄下糯俗。
- Library:存儲程序的默認(rèn)設(shè)置或其它狀態(tài)信息
Library/Caches:存放緩存文件
Library/Preferences: 存放的是UserDefaults存儲的信息 - tmp:創(chuàng)建臨時文件的地方
SDWebImage剖析
看下SDWebImageSequenceDiagram圖:
然后看下
sd_internalSetImageWithURL
:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context {
//validOperationKey
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
//根據(jù)validOperationKey取消相關(guān)的下載任務(wù)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
/*
關(guān)聯(lián)對象
源對象:self 關(guān)鍵字:imageURLKey 關(guān)聯(lián)的對象:url 一個關(guān)聯(lián)策略:OBJC_ASSOCIATION_RETAIN_NONATOMIC
關(guān)聯(lián)策略
OBJC_ASSOCIATION_ASSIGN = 0, <指定一個弱引用關(guān)聯(lián)的對象>
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,<指定一個強(qiáng)引用關(guān)聯(lián)的對象>
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, <指定相關(guān)的對象復(fù)制>
OBJC_ASSOCIATION_RETAIN = 01401, <指定強(qiáng)參考>
OBJC_ASSOCIATION_COPY = 01403 <指定相關(guān)的對象復(fù)制>
*/
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!(options & SDWebImageDelayPlaceholder)) {
//options存在并且options不是SDWebImageDelayPlaceholder尿褪,主線程設(shè)置占位圖
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
//url存在
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
//是否顯示進(jìn)度條
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
//執(zhí)行下載任務(wù)
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself;
//移除進(jìn)度條
[sself sd_removeActivityIndicator];
//self是否被釋放
if (!sself) { return; }
//圖片下載完成或者options為SDWebImageAvoidAutoSetImage block回調(diào)結(jié)果
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
//是否手動設(shè)置圖片
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
//self是否被釋放
if (!sself) { return; }
//自動設(shè)置
if (!shouldNotSetImage) {
//標(biāo)記為需要重新布局,異步調(diào)用layoutIfNeeded刷新布局得湘,不立即刷新杖玲,但layoutSubviews一定會被調(diào)用
[sself sd_setNeedsLayout];
}
//回調(diào)結(jié)果
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, error, cacheType, url);
}
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
//手動設(shè)置圖片
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
//圖片存在
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
//沒有圖片但是有占位圖
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
//是否有全局隊列(global_queue)
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_queue_async_safe(targetQueue, ^{
//設(shè)置圖片
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
});
}];
//
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
//移除進(jìn)度條
[self sd_removeActivityIndicator];
if (completedBlock) {
//錯誤
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
看下sd_cancelImageLoadOperationWithKey
:
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
SDOperationsDictionary *operationDictionary = [self operationDictionary];
//key獲取operations
id operations = operationDictionary[key];
//operations不為空
if (operations) {
//SDWebImageOperation數(shù)組
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
//SDWebImageOperation協(xié)議
[(id<SDWebImageOperation>) operations cancel];
}
//SDOperationsDictionary移除當(dāng)前key
[operationDictionary removeObjectForKey:key];
}
}
然后看下SDOperationsDictionary
是個啥:
//定義字典key為string value為id類型()
typedef NSMutableDictionary<NSString *, id> SDOperationsDictionary;
//懶加載
- (SDOperationsDictionary *)operationDictionary {
SDOperationsDictionary *operations = objc_getAssociatedObject(self, &loadOperationKey);
//存在返回
if (operations) {
return operations;
}
//不存在創(chuàng)建
operations = [NSMutableDictionary dictionary];
//關(guān)聯(lián)
objc_setAssociatedObject(self, &loadOperationKey, operations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return operations;
}
從loadImageWithURL
看看是如何下載圖片的:
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
//completedBlock不能為nil,如果你想預(yù)取圖像淘正,使用-[SDWebImagePrefetcher prefetchURLs]
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
//判斷url類型
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
//url是否合法
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;
//判斷url是否失敗過
BOOL isFailedUrl = NO;
if (url) {
/*
NSMutableSet:失敗url的一個集合
@synchronized結(jié)構(gòu)所做的事情跟鎖(lock)類似,它防止不同的線程同時執(zhí)行同一段代碼摆马。
*/
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
//url為空或者 url下載失敗并且沒有設(shè)置下載失敗重試
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
//失敗處理
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//NSMutableArray<SDWebImageCombinedOperation *>
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//根據(jù)url生成key
NSString *key = [self cacheKeyForURL:url];
//讀取緩存
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//operation取消
if (operation.isCancelled) {
//下載任務(wù)移除
[self safelyRemoveOperationFromRunning:operation];
return;
}
if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//圖片有緩存,options設(shè)置為SDWebImageRefreshCached 刷新緩存
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}
// download if no image or requested to refresh anyway, and download allowed by delegate
//下載圖片
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//下載
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
//operation不存在或者取消
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
} else if (error) {
//下載失敗
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];
if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
//失敗的url添加到failedURLs集合中
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//設(shè)置了失敗重試
if ((options & SDWebImageRetryFailed)) {
//將失敗的url從failedURLs集合中移除
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
//是否磁盤緩存
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options為刷新緩存鸿吆,但是沒有下載的圖片
if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//圖片下載完成
if (downloadedImage && finished) {
//緩存圖片
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
if (finished) {
//移除operation
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//移除operation
@synchronized(operation) {
// Need same lock to ensure cancelBlock called because cancel method can be called in different queue
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
}
} else if (cachedImage) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];
return operation;
}
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
//防止循環(huán)引用
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
//下載超時時間
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
//是否緩存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
//是否存儲Cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//header
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
operation.shouldDecompressImages = sself.shouldDecompressImages;
//證書
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//優(yōu)先級
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//addOperation
[sself.downloadQueue addOperation:operation];
/*
//以隊列的方式囤采,按照先進(jìn)先出的順序下載。這是默認(rèn)的下載順序
SDWebImageDownloaderFIFOExecutionOrder,
//以棧的方式惩淳,按照后進(jìn)先出的順序下載蕉毯。
SDWebImageDownloaderLIFOExecutionOrder
*/
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
緩存圖片storeImage
:
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
//image key缺一不可
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
//NSCache緩存(內(nèi)存緩存)
if (self.config.shouldCacheImagesInMemory) {
//圖片分辨率
NSUInteger cost = SDCacheCostForImage(image);
//存儲
[self.memCache setObject:image forKey:key cost:cost];
}
//磁盤緩存
if (toDisk) {
//異步存儲
dispatch_async(self.ioQueue, ^{
//自動釋放池
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, use PNG format
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:SDImageFormatPNG];
}
//磁盤緩存
[self storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}
//線程
[self checkIfQueueIsIOQueue];
//文件不存在創(chuàng)建
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
// disable iCloud backup
//禁用iCloud備份
if (self.config.shouldDisableiCloud) {
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}
如何從緩存中讀取圖片queryCacheOperationForKey
:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//key必須不為空
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
//首先從內(nèi)存中讀取
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
//磁盤讀取圖片data
NSData *diskData = nil;
if (image.images) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
//回調(diào)(SDImageCacheTypeMemory)
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
//圖片不在內(nèi)存中
NSOperation *operation = [NSOperation new];
//異步
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
//磁盤讀取
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//放入內(nèi)存
[self.memCache setObject:diskImage forKey:key cost:cost];
}
//回調(diào)(SDImageCacheTypeDisk)
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}
到這里,基本就差不多了思犁,SDWebImage緩沖用得是NSCatch和NSFileManager:
- NSCache:是系統(tǒng)提供的一種類似于
(NSMutableDictionary)的緩存代虾,它和NSMutableDictionary的不同:- NSCache具有自動刪除的功能,以減少系統(tǒng)占用的內(nèi)存激蹲;
- NSCache是線程安全的棉磨,不需要加線程鎖;
- 鍵對象不會像 NSMutableDictionary 中那樣被復(fù)制(鍵不需要實現(xiàn) NSCopying 協(xié)議)学辱。
- NSFileManager:是iOS中的文件管理類乘瓤。
有興趣的可以自己再看看這方面的文檔。