- Asynchronous image downloader with cache support as a UIImageView category
*一個(gè)異步的圖片下載與緩存的UIImageViewCategory
對于它究竟是如何工作的的榛,相信大家應(yīng)該或多或少都已經(jīng)有所了解丽涩。但是它內(nèi)部是怎么實(shí)現(xiàn),又有那些細(xì)節(jié)旋圆,我卻一直犯懶沒有真正的好好去研究過,最近不是很忙幌羞,于是我就仔細(xì)的研究了一下它的實(shí)現(xiàn)細(xì)節(jié)求冷,這里我源碼的版本為4.0.0,也就是當(dāng)前最新的版本辱挥。
**在這里我推薦大家去github
下載對應(yīng)的源碼,一邊看blog一邊看對應(yīng)的源碼边涕,最好再做上自己的注釋晤碘,這樣會看的更快,且做做筆記會加深自己的映像功蜓,SDWebImage:github
地址:https://github.com/rs/SDWebImage **
好了园爷,廢話不多說,直接來看代碼吧式撼。
下面的代碼是我們經(jīng)常使用的SDWebImage
的方法之一腮介,給imageView
傳入對應(yīng)的圖片url和占位圖片,它就幫我們實(shí)現(xiàn)了圖片的所有操作端衰。
點(diǎn)進(jìn)它的具體實(shí)現(xiàn)叠洗,可以看到它是一個(gè)UIImageView
的分類,分類的調(diào)用方法如下旅东,我已經(jīng)給對應(yīng)的參數(shù)做出了對應(yīng)的翻譯:
/**
* 使用一個(gè)url灭抑,占位圖片和自定義選項(xiàng)來設(shè)置imageView
* 下載是異步且緩存的
* url 圖像的url
* placeholder 占位圖片,初始化時(shí)被設(shè)置抵代,在請求結(jié)束時(shí)消失
* options 在下載圖片的時(shí)候使用的選項(xiàng)腾节,看SDWebImageOptions有哪些可能的值
* progressBlock 當(dāng)圖像下載時(shí)候調(diào)用的block,這個(gè)block在一個(gè)后臺隊(duì)列執(zhí)行
* completedBlock 當(dāng)操作結(jié)束時(shí)調(diào)用的block,這個(gè)block沒有返回值,把請求到的圖像作為第一個(gè)參數(shù)荤牍,如果發(fā)生錯(cuò)誤的話案腺,第一個(gè)參數(shù)為空,第二個(gè)參數(shù)會包含一個(gè)NSError對象康吵,第三個(gè)參數(shù)是一個(gè)bool值劈榨,指是從本地緩存還是從網(wǎng)絡(luò)來重新獲取圖像,第四個(gè)參數(shù)是圖片原始的url
*/
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
operationKey:nil
setImageBlock:nil
progress:progressBlock
completed:completedBlock];
}
相信大家對上面的參數(shù)晦嵌,并不陌生同辣,即使曾經(jīng)沒研究過,看到對應(yīng)的名稱和注釋也能大概猜出它們的作用惭载,這里唯一不太了解的應(yīng)該是options
的含義了旱函。
options
是一個(gè)枚舉,下面是options
對應(yīng)的值描滔,作用已經(jīng)添加在注釋中了
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* 默認(rèn)情況下,如果一個(gè)url在下載的時(shí)候失敗了,那么這個(gè)url會被加入黑名單并且library不會嘗試再次下載,這個(gè)flag會阻止library把失敗的url加入黑名單(簡單來說如果選擇了這個(gè)flag,那么即使某個(gè)url下載失敗了,sdwebimage還是會嘗試再次下載他.)
*/
SDWebImageRetryFailed = 1 << 0,
/**
* UI交互期間下載
* 導(dǎo)致延遲下載在UIScrollView減速的時(shí)候棒妨,(也就是你滑動(dòng)的時(shí)候scrollview不下載,你手從屏幕上移走,scrollview開始減速的時(shí)候才會開始下載圖片)
*/
SDWebImageLowPriority = 1 << 1,
/**
* 只進(jìn)行內(nèi)存緩存,不進(jìn)行磁盤緩存
*/
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* 這個(gè)標(biāo)志可以漸進(jìn)式下載,顯示的圖像是逐步在下載(就像你用瀏覽器瀏覽網(wǎng)頁的時(shí)候那種圖片下載,一截一截的顯示
*/
SDWebImageProgressiveDownload = 1 << 3,
/**
* 即使圖像緩存含长,也要遵守HTTP響應(yīng)緩存控制券腔,如果需要伏穆,可以從遠(yuǎn)程位置刷新圖像
* 磁盤緩存將由NSURLCache而不是SDWebImage處理,導(dǎo)致輕微的性能降低颅眶。
* 這個(gè)選項(xiàng)幫助處理在同樣的網(wǎng)絡(luò)請求地址下圖片的改變
* 如果刷新緩存的圖像蜈出,完成的block會在使用緩存圖像的時(shí)候調(diào)用,還會在最后的圖像被調(diào)用
* 當(dāng)你不能使你的URL靜態(tài)與嵌入式緩存
*/
SDWebImageRefreshCached = 1 << 4,
/**
* 在iOS4以上涛酗,如果app進(jìn)入后臺铡原,也保持下載圖像,這個(gè)需要取得用戶權(quán)限
* 如果后臺任務(wù)過期商叹,操作將被取消
*/
SDWebImageContinueInBackground = 1 << 5,
/**
* 操作cookies存儲在NSHTTPCookieStore通過設(shè)置NSMutableURLRequest.HTTPShouldHandleCookies = YES
*/
SDWebImageHandleCookies = 1 << 6,
/**
* 允許使用無效的SSL證書
* 用戶測試剖笙,生成情況下小心使用
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* 優(yōu)先下載
*/
SDWebImageHighPriority = 1 << 8,
/**
* 在加載圖片時(shí)加載占位圖。 此標(biāo)志將延遲加載占位符圖像弥咪,直到圖像完成加載过蹂。
*/
SDWebImageDelayPlaceholder = 1 << 9,
/**
* 我們通常不調(diào)用transformDownloadedImage代理方法在動(dòng)畫圖像上聚至,大多數(shù)情況下會對圖像進(jìn)行耗損
* 無論什么情況下都使用
*/
SDWebImageTransformAnimatedImage = 1 << 10,
/**
* 圖片在下載后被加載到imageView。但是在一些情況下扳躬,我們想要設(shè)置一下圖片(引用一個(gè)濾鏡或者加入透入動(dòng)畫)
* 使用這個(gè)來手動(dòng)的設(shè)置圖片在下載圖片成功后
*/
SDWebImageAvoidAutoSetImage = 1 << 11,
/**
* 圖像將根據(jù)其原始大小進(jìn)行解碼脆诉。 在iOS上贷币,此標(biāo)記會將圖片縮小到與設(shè)備的受限內(nèi)存兼容的大小役纹。
*/
SDWebImageScaleDownLargeImages = 1 << 12
};
看完上面的枚舉值字管,大家應(yīng)該還是不知道有什么作用,沒關(guān)系,接著往下看硫戈。
繼續(xù)往后可以看到下硕,最終它真正調(diào)用的是UIView+WebCache.h
的方法,這里就是要詳細(xì)講解的第一個(gè)方法嫩码,在下面的代碼中我已經(jīng)貼了一些注釋來方便講解:
- (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 {
// 獲取可用的operationKey
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
// 取消該key對應(yīng)的任務(wù)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
// 給該視圖的實(shí)例對象設(shè)置一個(gè)屬性
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 如果options不為SDWebImageDelayPlaceholder铸题,那么先把placeholder設(shè)置到該視圖上
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
if (url) {
// check if activityView is enabled or not
// 如果有url丢间,且設(shè)置顯示ActivityIndicator烘挫,那么顯示
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
// ??這里的operation不是繼承自NSOperation的柬甥,我們可以把它看做一個(gè)關(guān)聯(lián)視圖操作的對象苛蒲,我們稱它為op對象
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;
// 圖像下載成功后撤防,移除ActivityIndicator
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
// 如果有image且options為SDWebImageAvoidAutoSetImage且有completedBlock
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
// 在這里獲取到圖片寄月,且做一些加工的操作
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
// 如果有image漾肮,設(shè)置視圖的圖像
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
// 標(biāo)記設(shè)為需要布局
[sself sd_setNeedsLayout];
} else {
// image已經(jīng)嘗試獲取過了克懊,但是沒有從網(wǎng)絡(luò)端獲取到
// 如果options為SDWebImageDelayPlaceholder,當(dāng)前視圖設(shè)置為占位圖片
// 標(biāo)記設(shè)為需要布局
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
// 有completedBlock且下載finished為yes墙懂,將需要的參數(shù)傳出去
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
// 將現(xiàn)在的op對象加到對應(yīng)的視圖實(shí)例中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
// 如果url為空损搬,拋出錯(cuò)誤
dispatch_main_async_safe(^{
[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);
}
});
}
}
下面我們將一步步來解釋這些代碼的含義:
1.首先先獲取validOperationKey
巧勤,如果為空颅悉,那么就獲取到當(dāng)前類的名稱剩瓶,查看UIImageView+WebCache.h
對應(yīng)的傳入?yún)?shù),可以發(fā)現(xiàn)UIImageView
傳入的對應(yīng)validOperationKey
為nil
吠架,也就是說默認(rèn)情況下傍药,如果我們不直接給validOperationKey
賦值魂仍,它就為nil
擦酌,那么這里獲得的validOperationKey
一般也就是對應(yīng)類的class
赊舶,也就是說如果是UIImageView
調(diào)用這個(gè)方法笼平,那么對應(yīng)的validOperationKey
也就是UIImageView
。
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
2.取消該key
對應(yīng)的任務(wù)
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
什么情況锌唾,怎么還沒開始做事情就開始取消了晌涕?
在這里我們做一個(gè)標(biāo)記余黎,一會來解釋
??標(biāo)記1:- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key
是什么意思驯耻,為什么一來就取消炒考,有什么作用斋枢?
3.給該視圖的實(shí)例對象設(shè)置一個(gè)屬性瓤帚,這里的知識是使用了runtime
戈次,如果對runtime
不夠了解的,可以參看資料:讓你快速上手Runtime绊寻。
通俗點(diǎn)講:這里的作用就是給UIView
的實(shí)例添加了@property (nonatomic, strong) NSString *url;
澄步,只是這個(gè)屬性的獲取方式是通過key/value
的方式來獲得的村缸,url
這個(gè)value
對應(yīng)的key
為&imageURLKey
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
4.接下來就是設(shè)置placeholder
梯皿,如果不想讓SDWebImage
來幫你設(shè)置占位圖片东羹,就給它傳入setImageBlock
來自定義設(shè)置占位圖片百姓。
// 如果options不為SDWebImageDelayPlaceholder况木,那么先把placeholder設(shè)置到該視圖上
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
});
}
這里有兩個(gè)需要講解的
-
options & SDWebImageDelayPlaceholder
: &是按位與
舉個(gè)例子:a & b a=1 b=2 a== 0000 0001(二進(jìn)制) b== 0000 0010(二進(jìn)制) a & b = 0000 0000(二進(jìn)制)
放在這里就是求类,如果options
中包含SDWebImageDelayPlaceholder
屹耐,那么就不設(shè)置占位圖。 -
dispatch_main_async_safe
:這是一個(gè)定義的宏
如果當(dāng)前是主進(jìn)程犯眠,就直接執(zhí)行block筐咧,否則把block放到主進(jìn)程運(yùn)行量蕊。為什么要判斷是否是主進(jìn)程残炮?因?yàn)閕OS上任何UI的操作都在主線程上執(zhí)行缩滨,所以主進(jìn)程還有一個(gè)名字楷怒,叫做“UI進(jìn)程”鸠删。
ifndef dispatch_main_async_safe
define dispatch_main_async_safe(block)\
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
endif
5.下面的操作是根據(jù)url來加載網(wǎng)絡(luò)圖片刃泡,分為有`url`有值和`url`無值的情況
```obj
if (url) {
// check if activityView is enabled or not
if ([self sd_showActivityIndicatorView]) {
[self sd_addActivityIndicator];
}
__weak __typeof(self)wself = self;
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;
[sself sd_removeActivityIndicator];
if (!sself) {
return;
}
dispatch_main_async_safe(^{
if (!sself) {
return;
}
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
dispatch_main_async_safe(^{
[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);
}
});
}
先來分析
url
無值的情況禁添,也就是上面代碼中的else
老翘,可以很清晰的看到先會調(diào)用[self sd_removeActivityIndicator];
铺峭,根據(jù)名字我們大概能猜到是移除一個(gè)ActivityIndicator
卫键,然后會使用完成的block
在主線程拋出一個(gè)NSError
對象虱朵。-
現(xiàn)在來看
url
有值的情況,首先// 如果有url梆暮,且設(shè)置顯示ActivityIndicator惕蹄,那么顯示 if ([self sd_showActivityIndicatorView]) { [self sd_addActivityIndicator]; }
然后通過
SDWebImageManager
的單例對象調(diào)用下面的方法,返回了一個(gè)名為operation
的id
類型的對象- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url options:(SDWebImageOptions)options progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;
先不看這個(gè)方法的實(shí)現(xiàn)张峰,先猜一猜這個(gè)方法是做什么的喘批?
我想你肯定已經(jīng)猜到了饶深,這個(gè)方法就是下載圖片且給UIImageView
設(shè)置圖片的方法
現(xiàn)在先來看看這個(gè)方法完成的block中的代碼:
__strong __typeof (wself) sself = wself;
// 圖像下載成功后敌厘,移除ActivityIndicator
[sself sd_removeActivityIndicator];
// 如果self為nil朽合,直接返回
if (!sself) {
return;
}
然后如果獲取到圖片曹步,options
中包含SDWebImageAvoidAutoSetImage
讲婚,且完成的block
不為空的情況下筹麸,直接調(diào)用完成block
返回
// 如果有image且options為SDWebImageAvoidAutoSetImage且有completedBlock
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
// 在這里獲取到圖片竹捉,且做一些加工的操作
completedBlock(image, error, cacheType, url);
return;
}
如果沒有獲取到options
為SDWebImageAvoidAutoSetImage
块差,但是獲取到了image
,直接設(shè)置對應(yīng)視圖的image
else if (image) {
// 如果有image需五,設(shè)置視圖的圖像
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
// 標(biāo)記設(shè)為需要布局
[sself sd_setNeedsLayout];
}
然后就是當(dāng)image
沒有獲取到的時(shí)候的操作宏邮,如果之前設(shè)置的options
有SDWebImageDelayPlaceholder
(也就是延遲加載占位圖)蜜氨,那么現(xiàn)在也應(yīng)該把占位圖設(shè)置上了
else {
// image已經(jīng)嘗試獲取過了飒炎,但是沒有從網(wǎng)絡(luò)端獲取到
// 如果options為SDWebImageDelayPlaceholder笆豁,當(dāng)前視圖設(shè)置為占位圖片
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
}
最后闯狱,在所有的判斷結(jié)束以后哄孤,通過completedBlock
將對應(yīng)的參數(shù)傳遞出去
// 有completedBlock且下載finished為yes录豺,將需要的參數(shù)傳出去
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
在url不為nil的邏輯代碼的最后双饥,將前面生成的operation
和最開始獲取到的validOperationKey
設(shè)置到對應(yīng)的視圖咏花,也就是下面的代碼;韬病E锞铡统求!
在這里我們再做一個(gè)標(biāo)記,下面來解釋
??標(biāo)記2:- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key
又是什么意思另假,和上面的標(biāo)記1有什么關(guān)系怕犁?
// 將現(xiàn)在的op對象加到對應(yīng)的視圖實(shí)例中
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
上面對對應(yīng)的邏輯進(jìn)行大概的梳理奏甫,大家應(yīng)該學(xué)習(xí)到了一些扶檐,但是有些地方肯定還是不清楚款筑,所以看下面吧
下面是解決問題的時(shí)間
第一個(gè)問題
- 首先看到??標(biāo)記1和上面的??標(biāo)記2
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
在所有操作剛開始執(zhí)行的時(shí)候,視圖就執(zhí)行了個(gè)取消的操作解虱,最后又給視圖增加了一個(gè)operation
殴泰,這到底是怎么回事悍汛?
1.根據(jù)經(jīng)驗(yàn)离咐,如果要給一個(gè)UIImageView
設(shè)置image
宵蛀,那么肯定要獲取到對應(yīng)的image
术陶,如果這是一個(gè)網(wǎng)絡(luò)圖片梧宫,那么肯定是要將這個(gè)圖片下載,然后下載好了兆解,再將圖片設(shè)置到對應(yīng)的UIImageView
锅睛,相信大家對這個(gè)邏輯是沒有異議的现拒。
2.現(xiàn)在下載圖片對應(yīng)的操作就是id <SDWebImageOperation> operation
來執(zhí)行印蔬,一開始的取消操作就是取消了這樣一個(gè)任務(wù)
注意:這里的operation
可不是繼承自NSOperation
的對象侥猬,而是一個(gè)繼承自NSObject的對象退唠,你可以將它看做一個(gè)操作圖片更新的對象
3.看一下- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key
對應(yīng)的實(shí)現(xiàn)瞧预,首先通過[self operationDictionary]
獲取到存有operation
的字典(這里的字典也是通過runtime
動(dòng)態(tài)來添加的)垢油,然后通過對應(yīng)的key
取出對應(yīng)的operation
滩愁,調(diào)用cancel
來取消對應(yīng)的操作惊楼,然后通過key
移除對應(yīng)的operation
檀咙。
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
// operation的字典
SDOperationsDictionary *operationDictionary = [self operationDictionary];
id operations = operationDictionary[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];
}
}
4.接著看一下對應(yīng)的設(shè)置方法,設(shè)置方法中先調(diào)用了sd_cancelImageLoadOperationWithKey
棕诵,然后再將對應(yīng)的operation
添加到了字典中
- (void)sd_setImageLoadOperation:(nullable id)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];
if (operation) {
SDOperationsDictionary *operationDictionary = [self operationDictionary];
operationDictionary[key] = operation;
}
}
}
下面我舉個(gè)例子來講一下這么做的作用:
在常用的tableView
中cell
上有圖片是再常見不過的了校套,如下所示的這種cell
- 在我們使用
SDWebImage
給上面的cell
中的imageview
設(shè)置網(wǎng)絡(luò)圖片的時(shí)候侨把,圖片的下載是異步的秋柄,那么如果現(xiàn)在給當(dāng)前cell設(shè)置的為cell.imageview
為a.png
蠢正,隨著tableView
的滑動(dòng)骇笔,這個(gè)cell
會被復(fù)用,復(fù)用后現(xiàn)在cell.imageview
為b.png
嚣崭,這里的a.png
和b.png
都是從網(wǎng)絡(luò)上異步下載的,不是本地的資源圖片 - 一開始
cell
的index
為1雹舀,image
為a
旭旭,復(fù)用以后cell
的index
為6,image
為b
葱跋,按道理來說圖片應(yīng)該先為a
源梭,然后為b
娱俺,但是a
很大,b
很小废麻,b
都已經(jīng)下載好了荠卷,a
還沒有下載好,當(dāng)滑動(dòng)到顯示index
為6的cell
的時(shí)候烛愧,cell
的圖片先顯示的b
油宜,因?yàn)?code>b已經(jīng)下載好了,過了一會怜姿,a
也下載好了
那么神奇的事情發(fā)生了慎冤,index
為6的cell
中的圖片a
把b
覆蓋了,應(yīng)該顯示b
的變成顯示a
了 - 整個(gè)數(shù)據(jù)都亂了沧卢,這實(shí)在太可怕了
如果上面我舉的例子沒看懂蚁堤,請反復(fù)多看幾遍!但狭!
好披诗,我現(xiàn)在認(rèn)為你已經(jīng)看懂了~
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
上面??的兩個(gè)方法就是為了防止這種情況的發(fā)生撬即,因此先取消對應(yīng)的圖片操作,再重新添加呈队,剛開始先通過key
獲取operation
剥槐,如果有operation
對象---->取消。當(dāng)重新產(chǎn)生一個(gè)operation
對象以后宪摧,還是看對應(yīng)的字典中有沒有粒竖,有----> 取消(因?yàn)楝F(xiàn)在還沒將新產(chǎn)生的operation
添加到字典中),沒有--->operationDictionary[key] = operation;
绍刮,將這個(gè)operation
放到字典中温圆,這樣就可以保證一個(gè)視圖對象只有一個(gè)operation
在操作圖像
在這里也就是說如果設(shè)置了cell
的網(wǎng)絡(luò)圖片為b
,那么就取消掉之前的a
的相關(guān)操作,這樣就不會出現(xiàn)顯示錯(cuò)亂的問題了孩革。
作者的想法真的是很聰明呀!
第二個(gè)問題
在SDWebImage中常乘昵福可以看到options & SDWebImageRefreshCached
這種寫法,查看SDWebImageRefreshCached
的定義可以看到SDWebImageRefreshCached = 1 << 4
膝蜈。
例如:a=1 b=2 a== 0000 0001(二進(jìn)制) b== 0000 0010(二進(jìn)制) a & b = 0000 0000 (二進(jìn)制) 十進(jìn)制為0
也就是說SDWebImageRefreshCached
是將1左移4位的一個(gè)值锅移,二進(jìn)制表示為00010000,十進(jìn)制為16
在接下來的代碼中還會看到downloaderOptions | SDWebImageDownloaderLowPriority
饱搏,這種寫法是按位或非剃,也是位運(yùn)算的一種:
例如:a=5,b=11; 5 ==0000 0101 (二進(jìn)制) 10==0000 1011(二進(jìn)制) a | b== 0000 1111(二進(jìn)制) 十進(jìn)制為15
如果想了解更多的相關(guān)知識,可以參考這篇博客:按位與推沸,按位或
總結(jié)
我用了一張流程圖來表示這篇文章的內(nèi)容备绽,方便大家查看
以上是一些我的個(gè)人理解,如果有什么不對的地方也希望大家能夠指出鬓催,互相學(xué)習(xí)肺素!
這是SDWebImage源碼解析的第一篇,下一篇將會對下面產(chǎn)生
operation
的方法進(jìn)行分析宇驾,歡迎大家關(guān)注倍靡!
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock