YYWebImage工作原理介紹-----下載單張圖片

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的其他功能做介紹分析,如果你感興趣可以關注我。如果有任何問題可以留言我。

感謝

YYImage:https://github.com/ibireme/YYWebImage

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容