读码笔记-YYWebImage源码 (二) -YYWebImageOperation

YYWebImageOperation是一个自定义operation类,继承自NSOperation,本类读完之后可以很清晰的了解到作者在管理下载队列的时候的想法,以及如何自定义一个operation.

首先看暴露给我们的头文件

/**
 *  YYWebImageOperation 类是NSOperation的子类,用来通过请求获取图片, 
 
     @discussion 首先这个operation是异步的,你可以通过把operation添加到一个queue里面来让这个operation生效,或者直接调用'start'方法.当这个operation开始之后,将会做以下事情:
     1.从cache获取 图片,如果取到了,就返回'completion'block,并把图片传入block.
     2.通过图片URL开启一个请求,会通过'progress'参数来通知women图片下载的进度,并且如果在传入option的时候开启了progressive option,会在completionblock里面返回一个渐进显示的图片
     3.通过'transform'block来处理图片
     4.把图片丢到cache中并且在'completion'block返回
 */
@interface YYWebImageOperation : NSOperation

//图片请求
@property (nonatomic, strong, readonly) NSURLRequest *request;     ///< The image URL request.
//请求的相应结果
@property (nonatomic, strong, readonly) NSURLResponse *response;   ///< The response for request.
//理解为下载图片模式,具体见YYWebImageManager
@property (nonatomic, assign, readonly) YYWebImageOptions options; ///< The operation's option.
//缓存
@property (nonatomic, strong, readonly) YYImageCache *cache;       ///< The image cache.
//缓存key
@property (nonatomic, strong, readonly) NSString *cacheKey;        ///< The image cache key.

/**
 *  这个URL connection 是否是从 存储的认证里面授权查阅出来的.默认值为YES
    @discussion 这个值是NSURLConnectionDelegate的方法-connectionShouldUseCredentialStorage:的返回值
 */
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;

/**
 *  NSURLCredential类
 */
@property (nonatomic, strong) NSURLCredential *credential;

/**
 *  构造方法,会创建并返回一个新的operation
    你应该调用start方法来开启这个operation,或者把它加到一个operation queue
 *
 *  @param request    图片请求,不可为nil
 *  @param options    下载模式
 *  @param cache      图片缓存,传nil的话就禁用了缓存
 *  @param cacheKey   缓存key,传nil禁用图片缓存
 *  @param progress   下载进度block
 *  @param transform  这个block会在图片下载完成之前调用来让你对图片进行一些预处理,传nil禁用
 *  @param completion 图片下载完成后或者已经取消下载了调用
 *
 *  @return operation实例,出现错误的话就为nil
 */
- (instancetype)initWithRequest:(NSURLRequest *)request
                        options:(YYWebImageOptions)options
                          cache:(YYImageCache *)cache
                       cacheKey:(NSString *)cacheKey
                       progress:(YYWebImageProgressBlock)progress
                      transform:(YYWebImageTransformBlock)transform
                     completion:(YYWebImageCompletionBlock)completion NS_DESIGNATED_INITIALIZER;

- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

在实现文件中可以看到作者使用了自旋锁在保证了多线程同时访问本类的时候不会导致数据出错的同时性能高效.

static OSSpinLock URLBlacklistLock;//黑名单锁,OSSpinLock(自旋锁)大概是iOS中效率最高的一种锁了

在进行关键的操作的时候基本上全部做加锁处理,比如

/**
 *  把url添加进黑名单
 *
 *  @param url 
 */
static void URLInBlackListAdd(NSURL *url) {
    if (!url || url == (id)[NSNull null]) return;
    URLBlacklistInit();
    OSSpinLockLock(&URLBlacklistLock);
    [URLBlacklist addObject:url];
    OSSpinLockUnlock(&URLBlacklistLock);
}

首先提供给我们两个自定义的线程,都是生成的单例对象

///这里是一个全局的网络请求线程,提供给conllection的代理使用的
+ (NSThread *)_networkThread {
    static NSThread *thread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
        if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
            thread.qualityOfService = NSQualityOfServiceBackground;
        }
        [thread start];
    });
    return thread;
}

/// Global image queue, used for image reading and decoding.
///全局图片线程,用于读取图片解码
+ (dispatch_queue_t)_imageQueue {
#ifdef YYDispatchQueuePool_h
    return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
#else
    //最大线程数
    #define MAX_QUEUE_COUNT 16
    static int queueCount;
    static dispatch_queue_t queues[MAX_QUEUE_COUNT];
    static dispatch_once_t onceToken;
    static int32_t counter = 0;
    dispatch_once(&onceToken, ^{
        queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
        //如果线程数小于1,返回1,否则返回queueCount或者MAX_QUEUE_COUNT,取决于MAX_QUEUE_COUNT有没有值
        queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
        
        if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
            for (NSUInteger i = 0; i < queueCount; i++) {
                dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
                queues[i] = dispatch_queue_create("com.ibireme.image.decode", attr);
            }
        } else {
            for (NSUInteger i = 0; i < queueCount; i++) {
                queues[i] = dispatch_queue_create("com.ibireme.image.decode", DISPATCH_QUEUE_SERIAL);
                dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
            }
        }
    });
    int32_t cur = OSAtomicIncrement32(&counter);
    if (cur < 0) cur = -cur;
    return queues[(cur) % queueCount];
    #undef MAX_QUEUE_COUNT
#endif
}

紧接着是请求的构造方法与析构方法,需要注意的是在析构方法里面使用了递归锁NSRecursiveLock

/**
 *  构造方法
 *
 *  @param request    请求
 *  @param options    option
 *  @param cache      缓存
 *  @param cacheKey   缓存key
 *  @param progress   进入
 *  @param transform  预处理
 *  @param completion 完成
 *
 *  @return <#return value description#>
 */
- (instancetype)initWithRequest:(NSURLRequest *)request
                        options:(YYWebImageOptions)options
                          cache:(YYImageCache *)cache
                       cacheKey:(NSString *)cacheKey
                       progress:(YYWebImageProgressBlock)progress
                      transform:(YYWebImageTransformBlock)transform
                     completion:(YYWebImageCompletionBlock)completion {
    self = [super init];
    if (!self) return nil;
    if (!request) return nil;
    _request = request;
    _options = options;
    _cache = cache;
    //缓存key存在就使用,不存在使用url全路径
    _cacheKey = cacheKey ? cacheKey : request.URL.absoluteString;
    _shouldUseCredentialStorage = YES;
    _progress = progress;
    _transform = transform;
    _completion = completion;
    
    _executing = NO;
    _finished = NO;
    _cancelled = NO;
    _taskID = UIBackgroundTaskInvalid;
    return self;
}

/**
 *  析构方法里面使用了递归锁防止死锁,因为请求可能是有多个的.
    这个方法里面的操作可以保证开启了新的操作队列不会被旧的影响,同时把该清理的状态都归位完毕
 */
- (void)dealloc {
    [_lock lock];
    if (_taskID != UIBackgroundTaskInvalid) {
        [_YYSharedApplication() endBackgroundTask:_taskID];
        _taskID = UIBackgroundTaskInvalid;
    }
    
    
    //如果正在执行,设置取消为YES,结束为YES
    if ([self isExecuting]) {
        self.cancelled = YES;
        self.finished = YES;
        //如果存在连接,取消它,
        if (_connection) {
            [_connection cancel];
            
            //如果文件URL可达并且option是YYWebImageOptionShowNetworkActivity,那么请求数量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
        }
        //如果完成的回调存在,开启一个自动释放池,把参数传空,全置为nil,
        if (_completion) {
            @autoreleasepool {
                _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
            }
        }
    }
    [_lock unlock];
}

在重写的operation的start方法中开启当前operation开始执行,同时在重写operation的start,cancel,execute,finish四个方法的时候,对这些状态进行正确的处理,由于NSOperationisCancelled方法并不是能够实时监测的,所以在进行任何一个关键操作步骤的时候都要进行检测请求是否被取消掉了,如果取消,直接结束当前所有任务,并对状态值进行正确的赋值.
开启请求的函数

//开启一个操作,
- (void)_startOperation {
    //如果取消了直接返回,开启一个自动释放池完成以下操作
    if ([self isCancelled]) return;
    @autoreleasepool {
        // get image from cache
        //如果缓存存在,并且option不等于使用NSURLCache,并且option不是刷新缓存,那么直接通过缓存key从缓存中取取图片,同时设置缓存类型为内存缓存
        if (_cache &&
            !(_options & YYWebImageOptionUseNSURLCache) &&
            !(_options & YYWebImageOptionRefreshImageCache)) {
            UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
            if (image) {//取到了图片
                [_lock lock];
            
                if (![self isCancelled]) {//没有取消,
                    //如果已经完成,把图片,图片url,缓存类型,下载结果通过block传递回去
                    if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
                }
                //调用结束方法
                [self _finish];
                [_lock unlock];
                return;
            }
            //如果下载模式不等于YYWebImageOptionIgnoreDiskCache
            if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
                __weak typeof(self) _self = self;
                //开启一个同步的线程
                dispatch_async([self.class _imageQueue], ^{
                    __strong typeof(_self) self = _self;
                    if (!self || [self isCancelled]) return;//判空处理
                    //直接从磁盘缓存中通过cachekey获取图片
                    UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
                    //如果取到了图片
                    if (image) {
                        //先把图片再存进内存缓存
                        [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
                        //在网络线程调用_didReceiveImageFromDiskCache方法
                        [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];
}

//这个方法会确保跑在网络请求线程
- (void)_startRequest:(id)object {
    if ([self isCancelled]) return;
    @autoreleasepool {
        //如果模式是YYWebImageOptionIgnoreFailedURL,并且黑名单里面存在这个URL,
        if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
            //生成一个error
            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
            [_lock lock];
            //把error以及合适的参数传递给完成的回调,
            if (![self isCancelled]) {
                if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
            }
            [self _finish];
            [_lock unlock];
            return;
        }
        
        //如果url是可达的
        //这步计算文件size的
        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连接,这里为什么不直接使用delegate而需要通过重写proxy来试下呢?其实我们并不知道NSURLConnection内部delegate是weak/strong还是assign属性,这样做可以保证任何情况下都不会出错
            _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]];
            //url不可用,并且模式是YYWebImageOptionShowNetworkActivity,给网络请求数量+1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager incrementNetworkActivityCount];
            }
        }
        //结果出来了,解锁
        [_lock unlock];
    }
}

取消的方法实现

/**
 *  跑在网络线程上,被另外一个"cancel方法调用"
 */
- (void)_cancelOperation {
    @autoreleasepool {
        if (_connection) {
            
            //url不可用并且模式是YYWebImageOptionShowNetworkActivity,请求数量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
        }
        //取消操作并置空
        [_connection cancel];
        _connection = nil;
        //如果实现了完成的block,把相应参数与状态传递回去
        if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
        [self _endBackgroundTask];
    }
}

以下是通过不同的方式下载得到图片进行的处理

// runs on network thread
//从磁盘缓存中接受图片
- (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];
    }
}

/**
 *  从网络下载的图片
 *
 *  @param image <#image description#>
 */
- (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.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
                //模式是YYWebImageOptionIgnoreFailedURL的话,如果黑名单包括URL,给一个错误警告,否则把它加到黑名单
                if (_options & YYWebImageOptionIgnoreFailedURL) {
                    if (URLBlackListContains(_request.URL)) {
                        error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
                    } else {
                        URLInBlackListAdd(_request.URL);
                    }
                }
            }
            //把结果与error同时传递给block
            if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
            //结束
            [self _finish];
        }
        [_lock unlock];
    }
}

YYWebImage的下载是通过NSURLColleciton来实现的,自然需要在其代理方法里面做接收数据的操作,以下是其代理方法的实现,有点长

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {
    return _shouldUseCredentialStorage;
}

//即将发送请求验证,验证授权的一大堆乱七八糟东西,暂时不去管
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    @autoreleasepool {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if (!(_options & YYWebImageOptionAllowInvalidSSLCertificates) &&
                [challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {
                [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
            } else {
                NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            }
        } else {
            if ([challenge previousFailureCount] == 0) {
                if (_credential) {
                    [[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
                } else {
                    [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
                }
            } else {
                [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
            }
        }
    }
}

//即将缓存请求结果
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
    //如果为空,直接返回
    if (!cachedResponse) return cachedResponse;
    //如果模式=YYWebImageOptionUseNSURLCache,返回这个cache相应结果
    if (_options & YYWebImageOptionUseNSURLCache) {
        return cachedResponse;
    } else {
        
        //这里就是忽略NSURLCache了,作者有一套自己的缓存机制YYCache
        // ignore NSURLCache
        return nil;
    }
}

//请求已经收到相应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    @autoreleasepool {
        NSError *error = nil;
        //先判断是不是NSHTTPURLResponse相应类,是的话,先把状态码记录下来,如果出错,记录一个error
        if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
            NSHTTPURLResponse *httpResponse = (id) response;
            NSInteger statusCode = httpResponse.statusCode;
            if (statusCode >= 400 || statusCode == 304) {
                error = [NSError errorWithDomain:NSURLErrorDomain code:statusCode userInfo:nil];
            }
        }
        //有error了,取消连接,调用连接失败的方法同时把error传递过去
        if (error) {
            [_connection cancel];
            [self connection:_connection didFailWithError:error];
        } else {
            //通过length判断有内容,赋值
            if (response.expectedContentLength) {
                _expectedSize = (NSInteger)response.expectedContentLength;
                //没有直接返回-1
                if (_expectedSize < 0) _expectedSize = -1;
            }
            
            //给进度block赋值
            _data = [NSMutableData dataWithCapacity:_expectedSize > 0 ? _expectedSize : 0];
            if (_progress) {
                [_lock lock];
                if ([self isCancelled]) _progress(0, _expectedSize);
                [_lock unlock];
            }
        }
    }
}

//收到数据回调
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    @autoreleasepool {
        //如果取消了,直接返回
        [_lock lock];
        BOOL canceled = [self isCancelled];
        [_lock unlock];
        if (canceled) return;
        
        //如果data存在,拼接data,把计算data大小传递给进度block
        if (data) [_data appendData:data];
        if (_progress) {
            [_lock lock];
            if (![self isCancelled]) {
                _progress(_data.length, _expectedSize);
            }
            [_lock unlock];
        }
        
        /*--------------------------- progressive ----------------------------*/
        //根据模式判断是否需要返回进度以及是否需要渐进显示
        BOOL progressive = (_options & YYWebImageOptionProgressive) > 0;
        BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0;
        //如果没有实现了完成block,或者没有任何进度,直接返回
        if (!_completion || !(progressive || progressiveBlur)) return;
        //如果data长度小于一个字节,直接返回
        if (data.length <= 16) return;
        //其实就是length大于1,直接返回
        if (_expectedSize > 0 && data.length >= _expectedSize * 0.99) return;
        //如果设置了忽略渐进式加载,直接返回
        if (_progressiveIgnored) return;
        
        
        NSTimeInterval min = progressiveBlur ? MIN_PROGRESSIVE_BLUR_TIME_INTERVAL : MIN_PROGRESSIVE_TIME_INTERVAL;
        NSTimeInterval now = CACurrentMediaTime();
        if (now - _lastProgressiveDecodeTimestamp < min) return;
        
        //没有解码,初始化一个解码器
        if (!_progressiveDecoder) {
            _progressiveDecoder = [[YYImageDecoder alloc] initWithScale:[UIScreen mainScreen].scale];
        }
        //解码器更新数据
        [_progressiveDecoder updateData:_data final:NO];
        //如果调用取消方法,直接返回
        if ([self isCancelled]) return;
        
        
        if (_progressiveDecoder.type == YYImageTypeUnknown ||
            _progressiveDecoder.type == YYImageTypeWebP ||
            _progressiveDecoder.type == YYImageTypeOther) {
            _progressiveDecoder = nil;
            _progressiveIgnored = YES;
            return;
        }
        
        //只支持渐进式的JPEG图像和interlanced类型的PNG图像
        if (progressiveBlur) { // only support progressive JPEG and interlaced PNG
            if (_progressiveDecoder.type != YYImageTypeJPEG &&
                _progressiveDecoder.type != YYImageTypePNG) {
                _progressiveDecoder = nil;
                _progressiveIgnored = YES;
                return;
            }
        }
        if (_progressiveDecoder.frameCount == 0) return;
        //不存在渐进显示的话
        if (!progressiveBlur) {
            //从解码中获取图片帧
            YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
            if (frame.image) {
                [_lock lock];
                if (![self isCancelled]) {
                    //没有取消,把数据传递给完成block,
                    _completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
                    //给_lastProgressiveDecodeTimestamp赋值
                    _lastProgressiveDecodeTimestamp = now;
                }
                [_lock unlock];
            }
            return;
        } else {
            //解码之后发现是JPEG格式的
            if (_progressiveDecoder.type == YYImageTypeJPEG) {
                //如果表明了不是渐进式加载
                if (!_progressiveDetected) {
                    //从解码中取值
                    NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
                    NSDictionary *jpeg = dic[(id)kCGImagePropertyJFIFDictionary];
                    NSNumber *isProg = jpeg[(id)kCGImagePropertyJFIFIsProgressive];
                    if (!isProg.boolValue) {
                        _progressiveIgnored = YES;
                        _progressiveDecoder = nil;
                        return;
                    }
                    _progressiveDetected = YES;
                }
                //缩放长度为 接收到数据length - _progressiveScanedLength - 4
                NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4;
                //如果<=2,直接返回
                if (scanLength <= 2) return;
                NSRange scanRange = NSMakeRange(_progressiveScanedLength, scanLength);
                NSRange markerRange = [_data rangeOfData:JPEGSOSMarker() options:kNilOptions range:scanRange];
                _progressiveScanedLength = _data.length;
                if (markerRange.location == NSNotFound) return;
                if ([self isCancelled]) return;
                
            } else if (_progressiveDecoder.type == YYImageTypePNG) {//PNG类型图片
                if (!_progressiveDetected) {
                    //从解码中取值,解码,赋值
                    NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
                    NSDictionary *png = dic[(id)kCGImagePropertyPNGDictionary];
                    NSNumber *isProg = png[(id)kCGImagePropertyPNGInterlaceType];
                    if (!isProg.boolValue) {
                        _progressiveIgnored = YES;
                        _progressiveDecoder = nil;
                        return;
                    }
                    _progressiveDetected = YES;
                }
            }
            
            YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
            UIImage *image = frame.image;
            if (!image) return;
            //再次检查是否取消了
            if ([self isCancelled]) return;
            
            //最后一个像素没有填充完毕,以为没有下载成功,返回
            if (!YYCGImageLastPixelFilled(image.CGImage)) return;
            //进度++
            _progressiveDisplayCount++;
            
            CGFloat radius = 32;
            if (_expectedSize > 0) {
                radius *= 1.0 / (3 * _data.length / (CGFloat)_expectedSize + 0.6) - 0.25;
            } else {
                radius /= (_progressiveDisplayCount);
            }
            //处理图片
            image = [image yy_imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil];
            
            if (image) {
                [_lock lock];
                if (![self isCancelled]) {
                    //图片存在,给完成block赋值
                    _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
                    //给时间戳赋值
                    _lastProgressiveDecodeTimestamp = now;
                }
                [_lock unlock];
            }
        }
    }
}

//连接已经结束加载
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    @autoreleasepool {
        [_lock lock];
        _connection = nil;
        if (![self isCancelled]) {
            __weak typeof(self) _self = self;
            //开启一个异步线程
            dispatch_async([self.class _imageQueue], ^{
                __strong typeof(_self) self = _self;
                if (!self) return;
                //通过是否是YYWebImageOptionIgnoreImageDecoding模式判断是否需要解码
                BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0;
                //通过YYWebImageOptionIgnoreAnimatedImage模式判断是否需要显示动画小姑
                BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0;
                UIImage *image;
                BOOL hasAnimation = NO;
                //如果允许动画,通过YYImage这个类加载图片
                if (allowAnimation) {
                    image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale];
                    //如果需要解码,就解码了0.0
                    if (shouldDecode) image = [image yy_imageByDecoded];
                    //操作动画
                    if ([((YYImage *)image) animatedImageFrameCount] > 1) {
                        hasAnimation = YES;
                    }
                    //不允许动画
                } else {
                    //解码
                    YYImageDecoder *decoder = [YYImageDecoder decoderWithData:self.data scale:[UIScreen mainScreen].scale];
                    //直接取图片
                    image = [decoder frameAtIndex:0 decodeForDisplay:shouldDecode].image;
                }
                
                /*
                 If the image has animation, save the original image data to disk cache.
                 If the image is not PNG or JPEG, re-encode the image to PNG or JPEG for
                 better decoding performance.
                 */
                //如果是动图,保存原始图片数据到磁盘缓存,如果图片不是PNG或者JPEG格式,把图片转码成PNG或者JPEG格式,此举是为了得到更好的解码表现O.O
                YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
                switch (imageType) {
                    case YYImageTypeJPEG:
                    case YYImageTypeGIF:
                    case YYImageTypePNG:
                    case YYImageTypeWebP: { // save to disk cache,以上这几种图片村早磁盘
                        if (!hasAnimation) {
                            if (imageType == YYImageTypeGIF ||
                                imageType == YYImageTypeWebP) {
                                //没有动图,并且图片类型是GIF或者WebP,清空数据,给缓存转码
                                self.data = nil; // clear the data, re-encode for disk cache
                            }
                        }
                    } break;
                    default: {
                        self.data = nil; // clear the data, re-encode for disk cache
                    } break;
                }
                if ([self isCancelled]) return;//还要判断,自定义NSOperation真的好麻烦
                
                //如果预处理block在,并且有图片
                if (self.transform && image) {
                    //传递回调
                    UIImage *newImage = self.transform(image, self.request.URL);
                    //图片错了,清空
                    if (newImage != image) {
                        self.data = nil;
                    }
                    //正确GET
                    image = newImage;
                    if ([self isCancelled]) return;
                }
                
                //调用_didReceiveImageFromWeb方法表明从网络上下载的图片
                [self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
            });
            //如果图片URL不可用,并且模式是YYWebImageOptionShowNetworkActivity,网络请求数量-1
            if (![self.request.URL isFileURL] && (self.options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
        }
        [_lock unlock];
    }
}

//连接失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    @autoreleasepool {
        [_lock lock];
        if (![self isCancelled]) {
            //把失败信息也传递给完成block,因为失败也算完成了
            if (_completion) {
                _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
            }
            _connection = nil;
            _data = nil;
            //如果地址不可用,并且是YYWebImageOptionShowNetworkActivity模式,网络请求数量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
            //手动调一下结束方法
            [self _finish];
            
            //如果模式是忽略错误URL:YYWebImageOptionIgnoreFailedURL
            if (_options & YYWebImageOptionIgnoreFailedURL) {
                if (error.code != NSURLErrorNotConnectedToInternet &&
                    error.code != NSURLErrorCancelled &&
                    error.code != NSURLErrorTimedOut &&
                    error.code != NSURLErrorUserCancelledAuthentication) {
                    //加入黑名单
                    URLInBlackListAdd(_request.URL);
                }
            }
        }
        [_lock unlock];
    }
}

最下面是重写的NSOperation的状态值方法,主要看start跟cancel

/**
 *  开始这个NSOperation
 */
- (void)start {
    @autoreleasepool {
        [_lock lock];
        self.started = YES;//赋值开始标记为YES
        if ([self isCancelled]) {
            //如果这时候被取消了,调用取消方法
            [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
            self.finished = YES;//标记结束位YES
            //或者如果在准备开始,并且没有结束,并且没有运行中,执行以下操作
        } else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
            //请求失败
            if (!_request) {
                self.finished = YES;//记录结束
                if (_completion) {
                    //把错误信息传递给block
                    NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
                    _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
                }
            } else {
                //设置正在执行为YES
                self.executing = YES;
                //调用开始方法
                [self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
                //如果模式为YYWebImageOptionAllowBackgroundTask并且在后台,后台下载
                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];
    }
}

//取消方法
- (void)cancel {
    [_lock lock];
    //先检查是不是取消了,没有取消调用父类取消,设置自己取消为YES
    if (![self isCancelled]) {
        [super cancel];
        self.cancelled = YES;
        //如果正在执行中,设置执行中为NO,调用取消
        if ([self isExecuting]) {
            self.executing = NO;
            [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
        }
        //如果已经开始,直接标记结束,不做其他处理
        if (self.started) {
            self.finished = YES;
        }
    }
    [_lock unlock];
}

PS:
YYWebImage源码地址
我fork下来添加注释的版本github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容