前言:YYWebImage是YYKit系列中圖片請求的框架嫂伞。作者號稱是可以替代SDWebImage框架孔厉。至于優(yōu)秀的SDWebImage的框架在iOS中的地位想必你一定是知道的拯钻。再開始學習之前先奉上YYWebImage
框架的地址帖努。當然你也可以先看看YYWebImage
緩存框架的解析
本文準備從YYWebImageDemo中一個網(wǎng)絡請求開始,為了更好的解析這個請求的過程粪般。我們先將Demo中YYWebImageExample類請求url數(shù)組設置為單個
NSArray *links = @[
@"https://s-media-cache-ak0.pinimg.com/1200x/2e/0c/c5/2e0cc5d86e7b7cd42af225c29f21c37f.jpg"];
下面是這次請求的方法:
[_webImageView yy_setImageWithURL:url
placeholder:nil
options:YYWebImageOptionProgressiveBlur | YYWebImageOptionShowNetworkActivity | YYWebImageOptionSetImageWithFadeAnimation
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
if (expectedSize > 0 && receivedSize > 0) {
CGFloat progress = (CGFloat)receivedSize / expectedSize;
progress = progress < 0 ? 0 : progress > 1 ? 1 : progress;
if (_self.progressLayer.hidden) _self.progressLayer.hidden = NO;
_self.progressLayer.strokeEnd = progress;
}
}
transform:nil
completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
if (stage == YYWebImageStageFinished) {
_self.progressLayer.hidden = YES;
[_self.indicator stopAnimating];
_self.indicator.hidden = YES;
if (!image) _self.label.hidden = NO;
}
}];
我們先針對上面的請求方法看看相關的參數(shù)拼余,如果你使用過SDWebImage
,那這些參數(shù)你理解起來應該會比較容易亩歹。你最應該注意的是options枚舉匙监,因為它在整個流程的地方十分重要(作者給的備注英語通俗易懂,就再不做畫蛇添足的翻譯了):
本次網(wǎng)絡請求主要分為下面三個方面:
1.數(shù)據(jù)請求階段前根據(jù)option類型的操作小作。
這個階段主要的操作就是根據(jù)option類型來決定是否直接從內存里面取圖像亭姥。如果不從內存里面拿,是否需要先暫時顯示placeholder顾稀。這都是option來決定的达罗。我們現(xiàn)在要做的是跑通一個流程。Here we go!!!下面的代碼請結合流程一起看静秆。
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
//runtime 取value
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
//回到主線程
_yy_dispatch_sync_on_main_queue(^{
//如果option有YYWebImageOptionSetImageWithFadeAnimation且沒有YYWebImageOptionAvoidSetImage類型
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (!self.highlighted) {
//移除動畫
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {//當imageURL為空
//options沒有YYWebImageOptionIgnorePlaceHolder類型則直接把placeholder賦給image
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
//如果options 沒有YYWebImageOptionUseNSURLCache粮揉,YYWebImageOptionRefreshImageCache類型
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
//根據(jù)key去YYCache里面取UIImage
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
//如果options 沒有YYWebImageOptionAvoidSetImage類型
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.image = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
//如果options 沒有YYWebImageOptionUseNSURLCache
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
//============上面是文中數(shù)據(jù)請求階段前根據(jù)option類型的操作====================================
//因為為了避免下載大尺寸文件或者圖片內存爆掉,所以NSURLConnection會把文件或圖片分成多段回調抚笔。下面就是拿到總expectedSize和每次接收到的receivedSize的block聲明
__weak typeof(self) _self = self;
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
//====================================_completion==================================
// 完成的回調聲明
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
dispatch_async(dispatch_get_main_queue(), ^{
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
if (setImage && self && !sentinelChanged) {
BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.image = image;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
//====================================_completion==================================
//將相關參數(shù)和聲明的block傳過去
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
2.加入隊列數(shù)據(jù)請求階段
這部分代碼跳轉太多扶认,便不貼相關代碼了。你運行代碼應該能獲取下圖相關的流程殊橙。
3.NSURLConnection回調的處理
// 在構建connection會被響應辐宾。如果這個connection需要根據(jù)NSURLCredentialStorage中的權限進行構建,那么就返回YES
// 默認是YES膨蛮,想要修改螃概,需要用戶自己指定self.shouldUseCredentialStorage值
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
//客戶端開始認證挑戰(zhàn)(Authentication Challenge)
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
//返回相應頭的信息。重要的有statusCode鸽疾,expectedContentLength吊洼。回調給size制肮,數(shù)據(jù)接收的開始階段冒窍。
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//文件或者圖片分割成多份回調
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//文件下載完畢后的回調
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
//下載失敗递沪,失敗回調
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
類
流程跑完了,我們現(xiàn)在來看下類圖:
YYWebImage使用YYImageCache作緩存(YYCache上層)综液,YYImage做解碼相關工作款慨。利用Category分類做提供接口。而
_YYWebImageSetter
是一個私有類谬莹,為categories提供接口檩奠,值得一提的是它使用OSAtomicIncrement32
來原子操作級別地遞增_sentinel。下面我們主要來看看YYWebImageManager
附帽,YYWebImageOperation
埠戳。
YYWebImageManager
YYWebImageManager
是YYWebImage的管理類,主要提供了下面管理:
1.圖片操作的枚舉YYWebImageOptions
2.圖片的來源的枚舉YYWebImageFromType
3.請取圖片狀態(tài)的枚舉YYWebImageStage
4.進度的回調block YYWebImageProgressBlock
5.對圖片附加的操作block YYWebImageTransformBlock
6.請求圖片完成的block YYWebImageCompletionBlock
YYWebImageOperation
YYWebImageOperation
是NSOperation
的子類蕉扮,是對一個請求任務的包裝來從URL獲取圖片整胃。它的屬性executing
,finished
喳钟,cancelled
屁使,started
控制著任務的狀態(tài)。通過把任務添加到隊列奔则,或者手動調用start
方法執(zhí)行蛮寂。當一個任務開始:
1.從磁盤里面請求圖片,如果存在易茬。返回并completion block回調酬蹋。
2.開始URL connection請求圖片,如果設置了YYWebImageProgressBlock疾呻,則返回每一段的Progress
3.如果設置了YYWebImageTransformBlock除嘹,則依據(jù)block 處理圖片
4.把圖片存入磁盤。返回并completion block回調岸蜗。