YYWebImage簡介
YYWebImage是由ibireme開發(fā)的YYkit其中的一個庫霉囚,專門處理圖像榜跌。github傳送門:YYWebImage钓葫。他的出現(xiàn)是為了替代 SDWebImage票顾、PINRemoteImage豆同、FLAnimatedImage 等開源框架含鳞。在YYImage出來之前,我們的圖片下載和動態(tài)圖片展示需要SD和FL兩個庫去處理≡姹В現(xiàn)在YYImage的出現(xiàn)使得統(tǒng)一圖像庫成為可能佳晶。這篇文章會根據(jù)YYImage下載單張圖的生命周期為主線簡單的介紹一下轿秧,YYImage各個模塊是如何協(xié)同合作的淤刃。
YYWebImage使用
先引入頭文件:
#import <YYWebImage/YYWebImage.h>
然后創(chuàng)建一個UIImageVIew,YYWebImage有對 UIImageVIew 的Category 方法支持津滞。所以只要一句話:
imvAd.cbd_imageURL=[NSURL URLWithString:@"http://ss.bdimg.com/static/superman/img/logo/bd_logo1_31bdc765.png"];
這樣圖片就下載下來并展示出來触徐,api相當簡單撞鹉。接下來我們就深入源代碼鸟雏,看看YYimage到底做了些什么览祖。
UIImageView+YYWebImage類
當我們點進方法以后展蒂,發(fā)現(xiàn)這是一個Category 方法,里面提供了很多方法柳骄,單都是基于一個方法擴展的:
- (void)setYy_imageURL:(NSURL *)imageURL {
[self yy_setImageWithURL:imageURL
placeholder:nil
options:kNilOptions
manager:nil
progress:nil
transform:nil
completion:nil];
}
里面可以傳圖片的url耐薯,默認圖片鸠踪,動畫效果類型营密,下載類型评汰,下載管理器被去,提供的block:進度條回調,完成回調奖唯。
這個方法是整個Category的核心惨缆,代碼比較長,我們分開看丰捷。
首先是初始化一個YYWebImageManager坯墨,然后動態(tài)的添加_YYWebImageSetter屬性,為的是管控整個YYImage的下載病往,查找有沒有相同的url在下載捣染,如果有的話就要取消操作,確保同一個url只有一個隊列在下載處理:
_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];
緊接著YYImage會切到主線程中停巷,做一些配置耍攘,設置一下動畫,設置一下默認圖片,根據(jù)剛才的options從不同的地方獲取圖片:
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (!self.highlighted) {
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
return;
}
// get the image from memory as quickly as possible
UIImage *imageFromMemory = nil;
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.image = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
這里要注意的是他會在YYImageCacheMemory中尋找一次圖片,也就是他默認先在內存中找一下。但是默認的方法中卖氨,我們的圖片是存在YYImageCacheDisk中的系吭。我們點開路徑可以看到则吟,他是在Library/Caches/com.ibireme.yykit/images中的數(shù)據(jù)庫里寨昙,我們查看一下數(shù)據(jù)庫發(fā)現(xiàn):
已經(jīng)存好了。(因為我這里已經(jīng)下載過這張圖片了)
所以現(xiàn)在YYImage并沒有找到圖片缆巧,繼續(xù)往執(zhí)行,他開出一個異步線程進行下載:
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;
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);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
這里的核心是:
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
YYWebImageOperation.h類
點進去以后看祠够,發(fā)現(xiàn)又是一坨虑瀑,仔細看會發(fā)現(xiàn)痛侍,大部分是賦值和一些判斷君丁,最關鍵的創(chuàng)建一個請求去獲取圖片:
NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
作者對NSOperation進行了自定義扒最,他創(chuàng)建了一個YYWebImageOperation强挫,這是YYImage請求的核心稠诲。首先他重寫了
start:
- (void)start {
@autoreleasepool {
[_lock lock];
self.started = YES;
if ([self isCancelled]) {
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
self.finished = YES;
} else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
if (!_request) {
self.finished = YES;
if (_completion) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
} else {
self.executing = YES;
[self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) {
__weak __typeof__ (self) _self = self;
if (_taskID == UIBackgroundTaskInvalid) {
_taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (_self) self = _self;
if (self) {
[self cancel];
self.finished = YES;
}
}];
}
}
}
}
[_lock unlock];
}
}
這個start方法主要是對隊列的一些控制跨释,他會再分出一個網(wǎng)絡線程networkThread去做網(wǎng)絡請求工作缆娃。我們順著往下看崇渗,他進入了:
-(void)_startOperation
這里還沒進入請求金拒,YYImage會去查看硬盤內存中的圖片幢码,如果有就會執(zhí)行:
UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
如果沒有的話贞铣,就會去執(zhí)行_startRequest方法辕坝,然后我們就看到了最重要的一段代碼:
if (![self isCancelled]) {
_connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]];
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[YYWebImageManager incrementNetworkActivityCount];
}
}
這里請求還是用的快要淘汰的NSURLConnection纺酸,- -讓人潸然淚下....接下來就是一些回調了用含。整個流程結束橡类!
整個流程圖如下:
總結
由此看出YYImage使用非常簡單,但內部構造比較復雜咆课。主要的核心模塊是:_YYWebImageSetter末誓,YYImageCache扯俱,YYWebImageManager,YYWebImageOperation基显。分工有序蘸吓,結構清晰,美中不足的是請求方式還是基于快要淘汰的NSURLConnection撩幽。不過要修改起來也比較容易库继,之后我會對YYImage的其他功能做介紹分析,如果你感興趣可以關注我窜醉。如果有任何問題可以留言我宪萄。