美文网首页
YYKit__ YYWebImage__源码分析

YYKit__ YYWebImage__源码分析

作者: sea777777 | 来源:发表于2016-09-24 10:46 被阅读493次

    底层使用NSUrlConnection方式,多线程异步加载网络图片;

    • 加载时先从内存缓存区寻找,再从磁盘缓存区寻找,如果没有,从服务器加载,加载完先放入内存缓存区,再放入磁盘,再进行显示;
    
    基本使用:
    
        /* 加载方式:渐变处理,显示方式:模糊->清晰,显示网络状态,
         progress:处理进度条
         completion:下载完成回调
         placeholder:占位图
         **/
      [_webImageView 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;
                               }
                           }];
    
    
    
    - (void)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];
        
        
        /*  
         YYWebImage 类绑定 setter
            这里需要的仅仅是 _YYWebImageSetterKey 的地址,所以并没有给 _YYWebImageSetterKey 初始化值,因为并不关心值是什么
         **/
        _YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
        if (!setter) {
            setter = [_YYWebImageSetter new];
            objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        
        //取消正在处理的 NSOperation,并设置新的 url , setter.sentinel 自增1
        int32_t sentinel = [setter cancelWithNewURL:imageURL];
        
        
        //涉及ui更新 主队列异步执行
        dispatch_async_on_main_queue(^{
            if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
                !(options & YYWebImageOptionAvoidSetImage)) {
                if (!self.highlighted) {
                    //移除正在播放的动画
                    [self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
                }
            }
            
            if (!imageURL) {
                //如果url为空,并且需要设置占位图
                if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
                    self.image = placeholder;
                }
                return;
            }
            
            //首先从缓存中获取image
            UIImage *imageFromMemory = nil;
            
            //不用url缓存,并没有设置刷新缓存(从缓存中找image)
            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;
            }
    
            __weak typeof(self) _self = self;
            
            /*
             创建串行队列 “com.ibireme.yykit.webimage.setter”,并异步执行串行队列
             **/
            dispatch_async([_YYWebImageSetter setterQueue], ^{
               
                YYWebImageProgressBlock _progress = nil;
                if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
                    /*
                      progress 需要更新视图,所以需要在主队列中执行
                     **/
                    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(), ^{
                        
                        
                        /*
                         newSentinel 是 setter setOperationWithSentinel 方法执行之后的标识,
                         一般 newSentinel 和 weakSetter.sentinel 是相等的,除非setter实例被dealloc ,
                                则 weakSetter.sentinel != newSentinel
                         **/
                        BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
                        // 首次设置image
                        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;
            });
        });
    }
    
    
    
    //创建 operation
    - (int32_t)setOperationWithSentinel:(int32_t)sentinel
                                    url:(NSURL *)imageURL
                                options:(YYWebImageOptions)options
                                manager:(YYWebImageManager *)manager
                               progress:(YYWebImageProgressBlock)progress
                              transform:(YYWebImageTransformBlock)transform
                             completion:(YYWebImageCompletionBlock)completion {
        //失败直接返回
        if (sentinel != _sentinel) {
            if (completion) completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
            return _sentinel;
        }
        
        //创建下载操作 ,manager 把任务加到队列里,或默认开启
        NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
        //创建 operation 失败
        if (!operation && completion) {
            NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"YYWebImageOperation create failed." };
            completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageFinished, [NSError errorWithDomain:@"com.ibireme.yykit.webimage" code:-1 userInfo:userInfo]);
        }
        
        dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
        if (sentinel == _sentinel) {
            //_operation 指向上一次 operation,当执行下一次操作前,取消上一次operation
            if (_operation) [_operation cancel];
            _operation = operation;
    
            sentinel = OSAtomicIncrement32(&_sentinel);
        } else {
            [operation cancel];
        }
        dispatch_semaphore_signal(_lock);
        return sentinel;
    }
    
    
    
    
    //创建 request 并加入队列
    - (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
                                         options:(YYWebImageOptions)options
                                        progress:(YYWebImageProgressBlock)progress
                                       transform:(YYWebImageTransformBlock)transform
                                      completion:(YYWebImageCompletionBlock)completion {
        
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        //超时时间
        request.timeoutInterval = _timeout;
        //是否使用 cookies(cookie可做持久化缓存)
        request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0;
        request.allHTTPHeaderFields = [self headersForURL:url];
        
        /*
         是否开启多通道
         The WWDC session described it as a huge performance win if the servers support it.
         WWDC 描述,如果服务器支持的话,这会有很大的性能提升;
         如果服务器支持通道操作的话,性能会有所提升,因为所有后续的请求不必等待第一个完成;
         http://stackoverflow.com/questions/14810890/what-are-the-disadvantages-of-using-http-pipelining
         **/
        request.HTTPShouldUsePipelining = YES;
        
        /* 设置缓存策略
         NSURLRequestUseProtocolCachePolicy:使用缓存;
         NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存,重新加载
         **/
        request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
            NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        
        
        YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request
                                                                              options:options
                                                                                cache:_cache
                                                                             cacheKey:[self cacheKeyForURL:url]
                                                                             progress:progress
                                                                            transform:transform ? transform : _sharedTransformBlock
                                                                           completion:completion];
    
        if (_username && _password) {
            //身份认证,服务器要求用户名和密码时需要提供
            operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession];
        }
        if (operation) {
            //可以把队列设置nil  , 这样会直接执行任务
            NSOperationQueue *queue = _queue;
            if (queue) {
                [queue addOperation:operation];
            } else {
                [operation start];
            }
        }
        return operation;
    }
    
    
    
    
    
    //开始执行operation
    - (void)start {
        @autoreleasepool {
            [_lock lock];
            self.started = YES;
            if ([self isCancelled]) {
                //在 network 线程上执行网络相关操作
                [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) && ![UIApplication isAppExtension]) {
                        __weak __typeof__ (self) _self = self;
                        
                        //如果当前任务是无效的任务请求(如执行 _cancelOperation 操作) 则需要取消这个操作(取消必选执行,所以把 self转成 __strong)
                        if (_taskID == UIBackgroundTaskInvalid) {
                            _taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{
                                //避免 _self 执行中被释放,因为这里要执行 [_connection cancel] 操作,必须要执行完毕才能释放
                                __strong __typeof (_self) self = _self;
                                if (self) {
                                    [self cancel];
                                    self.finished = YES;
                                }
                            }];
                        }
                    }
                }
            }
            
            
            [_lock unlock];
        }
    }
    
    
    
    //取消操作,执行 _completion,并结束正在执行的后台任务
    - (void)_cancelOperation {
        @autoreleasepool {
            if (_connection) {
                if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                    [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
                }
            }
            [_connection cancel];
            _connection = nil;
            if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
            [self _endBackgroundTask];
        }
    }
    
    
    //结束当前任务
    - (void)_endBackgroundTask {
        [_lock lock];
        if (_taskID != UIBackgroundTaskInvalid) {
            
            // 注意 :必须调用此方法结束任务,否则app进入进入后台模式,如果有长时间运行的任务,系统会杀死当前应用
            [[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
            
            // 标记当前任务id为不可用, 这个表示会再 start 里进行判断
            _taskID = UIBackgroundTaskInvalid;
        }
        [_lock unlock];
    }
    
    
    
    // 从缓存中取image,并执行 _completion
    - (void)_startOperation {
        if ([self isCancelled]) return;
        @autoreleasepool {
            // 先从缓存获取image
            if (_cache &&
                !(_options & YYWebImageOptionUseNSURLCache) &&
                !(_options & YYWebImageOptionRefreshImageCache)) {
                
                //先取内存
                UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
                if (image) {
                    [_lock lock];
                    
                    //???? 第一步不是已经过滤了吗?
                    if (![self isCancelled]) {
                        if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
                    }
                    [self _finish];
                    [_lock unlock];
                    return;
                }
                
                //再取磁盘缓存,取磁盘缓存耗时,异步执行,取到后存入内存cache,取不到从服务器获取
                if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
                    __weak typeof(self) _self = self;
                    dispatch_async([self.class _imageQueue], ^{
                        __strong typeof(_self) self = _self;
                        if (!self || [self isCancelled]) return;
                        UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
    
                //如果从缓存取到image,则将磁盘缓存放入内存缓存,以便下次获取,
                        //如果没有获取到image,则从服务器加载image
                        if (image) {
    
                            [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
                            //执行 _completion
                            [self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
                        } else {
                            [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
                        }
                    });
                    return;
                }
            }
        }
        [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
    }
    
    
    
    
    //从磁盘加载完图片后,执行 _completion ,结束流程
    - (void)_didReceiveImageFromDiskCache:(UIImage *)image {
        @autoreleasepool {
            [_lock lock];
            if (![self isCancelled]) {
                // 磁盘取到值,结束流程,取不到值,继续请求
                if (image) {
                    if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil);
                    [self _finish];
                } else {
                    [self _startRequest:nil];
                }
            }
            [_lock unlock];
        }
    }
    
            
    
    
    // 创建 _connection
    - (void)_startRequest:(id)object {
        if ([self isCancelled]) return;
        @autoreleasepool {
            //URL 有误,或url在黑名单内,则结束流程,执行 _completion
            if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
                [_lock lock];
                if (![self isCancelled]) {
                    if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
                }
                [self _finish];
                [_lock unlock];
                return;
            }
            
            //获取文件下载总大小(bytes), _expectedSize 用于计算加载的百分比
            if (_request.URL.isFileURL) {
                NSArray *keys = @[NSURLFileSizeKey];
                NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil];
                NSNumber *fileSize = attr[NSURLFileSizeKey];
                _expectedSize = fileSize ? fileSize.unsignedIntegerValue : -1;
            }
            
            // request image from web
            [_lock lock];
            if (![self isCancelled]) {
                _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[YYWeakProxy proxyWithTarget:self]];
                if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                    [[UIApplication sharedExtensionApplication] incrementNetworkActivityCount];
                }
            }
            [_lock unlock];
        }
    }
    
    
    
    
    
    
    
    
    
    
    //从服务器接收完图片
    - (void)_didReceiveImageFromWeb:(UIImage *)image {
        @autoreleasepool {
            [_lock lock];
            if (![self isCancelled]) {
                
                //从服务器加载完的图片先放到内存缓存中
                if (_cache) {
                    if (image || (_options & YYWebImageOptionRefreshImageCache)) {
                        NSData *data = _data;
                        
                        //耗时操作需要异步执行
                        dispatch_async([YYWebImageOperation _imageQueue], ^{
                            [_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll];
                        });
                    }
                }
                _data = nil;
                NSError *error = nil;
                if (!image) {
                    error = [NSError errorWithDomain:@"com.ibireme.yykit.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
                    if (_options & YYWebImageOptionIgnoreFailedURL) {
                        if (URLBlackListContains(_request.URL)) {
                            error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
                        } else {
                            
                            //用户没有忽略此url ,但是加载失败,则加入黑名单,则下次不进行加载
                            URLInBlackListAdd(_request.URL);
                        }
                    }
                }
                
                //回调 _completion
                if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
                
                // 结束后台正在执行的任务;
                [self _finish];
            }
            [_lock unlock];
        }
    }
                
    

    相关文章

      网友评论

          本文标题:YYKit__ YYWebImage__源码分析

          本文链接:https://www.haomeiwen.com/subject/hadxyttx.html