YYWebImage簡介
YYWebImage是由ibireme開發的YYkit其中的一個庫,專門處理圖像。github傳送門:YYWebImage。他的出現是為了替代 SDWebImage、PINRemoteImage、FLAnimatedImage 等開源框架。在YYImage出來之前,我們的圖片下載和動態圖片展示需要SD和FL兩個庫去處理。現在YYImage的出現使得統一圖像庫成為可能。這篇文章會根據YYImage下載單張圖的生命周期為主線簡單的介紹一下,YYImage各個模塊是如何協同合作的。
YYWebImage使用
先引入頭文件:
#import <YYWebImage/YYWebImage.h>
然后創建一個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類
當我們點進方法以后,發現這是一個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,然后動態的添加_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會切到主線程中,做一些配置,設置一下動畫,設置一下默認圖片,根據剛才的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中的數據庫里,我們查看一下數據庫發現:
已經存好了。(因為我這里已經下載過這張圖片了)
所以現在YYImage并沒有找到圖片,繼續往執行,他開出一個異步線程進行下載:
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類
點進去以后看,發現又是一坨,仔細看會發現,大部分是賦值和一些判斷,最關鍵的創建一個請求去獲取圖片:
NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
作者對NSOperation進行了自定義,他創建了一個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方法主要是對隊列的一些控制,他會再分出一個網絡線程networkThread去做網絡請求工作。我們順著往下看,他進入了:
-(void)_startOperation
這里還沒進入請求,YYImage會去查看硬盤內存中的圖片,如果有就會執行:
UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
如果沒有的話,就會去執行_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的其他功能做介紹分析,如果你感興趣可以關注我。如果有任何問題可以留言我。