讀碼筆記-YYWebImage源碼 (二) -YYWebImageOperation

YYWebImageOperation是一個自定義operation類,繼承自NSOperation,本類讀完之后可以很清晰的了解到作者在管理下載隊列的時候的想法,以及如何自定義一個operation.

首先看暴露給我們的頭文件

/**
 *  YYWebImageOperation 類是NSOperation的子類,用來通過請求獲取圖片, 
 
     @discussion 首先這個operation是異步的,你可以通過把operation添加到一個queue里面來讓這個operation生效,或者直接調(diào)用'start'方法.當這個operation開始之后,將會做以下事情:
     1.從cache獲取 圖片,如果取到了,就返回'completion'block,并把圖片傳入block.
     2.通過圖片URL開啟一個請求,會通過'progress'參數(shù)來通知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;

/**
 *  構造方法,會創(chuàng)建并返回一個新的operation
    你應該調(diào)用start方法來開啟這個operation,或者把它加到一個operation queue
 *
 *  @param request    圖片請求,不可為nil
 *  @param options    下載模式
 *  @param cache      圖片緩存,傳nil的話就禁用了緩存
 *  @param cacheKey   緩存key,傳nil禁用圖片緩存
 *  @param progress   下載進度block
 *  @param transform  這個block會在圖片下載完成之前調(diào)用來讓你對圖片進行一些預處理,傳nil禁用
 *  @param completion 圖片下載完成后或者已經(jīng)取消下載了調(diào)用
 *
 *  @return operation實例,出現(xiàn)錯誤的話就為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;

在實現(xiàn)文件中可以看到作者使用了自旋鎖在保證了多線程同時訪問本類的時候不會導致數(shù)據(jù)出錯的同時性能高效.

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);
}

首先提供給我們兩個自定義的線程,都是生成的單例對象

///這里是一個全局的網(wǎng)絡請求線程,提供給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
    //最大線程數(shù)
    #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;
        //如果線程數(shù)小于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;
}

/**
 *  析構方法里面使用了遞歸鎖防止死鎖,因為請求可能是有多個的.
    這個方法里面的操作可以保證開啟了新的操作隊列不會被舊的影響,同時把該清理的狀態(tài)都歸位完畢
 */
- (void)dealloc {
    [_lock lock];
    if (_taskID != UIBackgroundTaskInvalid) {
        [_YYSharedApplication() endBackgroundTask:_taskID];
        _taskID = UIBackgroundTaskInvalid;
    }
    
    
    //如果正在執(zhí)行,設置取消為YES,結束為YES
    if ([self isExecuting]) {
        self.cancelled = YES;
        self.finished = YES;
        //如果存在連接,取消它,
        if (_connection) {
            [_connection cancel];
            
            //如果文件URL可達并且option是YYWebImageOptionShowNetworkActivity,那么請求數(shù)量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
        }
        //如果完成的回調(diào)存在,開啟一個自動釋放池,把參數(shù)傳空,全置為nil,
        if (_completion) {
            @autoreleasepool {
                _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
            }
        }
    }
    [_lock unlock];
}

在重寫的operation的start方法中開啟當前operation開始執(zhí)行,同時在重寫operation的start,cancel,execute,finish四個方法的時候,對這些狀態(tài)進行正確的處理,由于NSOperationisCancelled方法并不是能夠?qū)崟r監(jiān)測的,所以在進行任何一個關鍵操作步驟的時候都要進行檢測請求是否被取消掉了,如果取消,直接結束當前所有任務,并對狀態(tài)值進行正確的賦值.
開啟請求的函數(shù)

//開啟一個操作,
- (void)_startOperation {
    //如果取消了直接返回,開啟一個自動釋放池完成以下操作
    if ([self isCancelled]) return;
    @autoreleasepool {
        // get image from cache
        //如果緩存存在,并且option不等于使用NSURLCache,并且option不是刷新緩存,那么直接通過緩存key從緩存中取取圖片,同時設置緩存類型為內(nèi)存緩存
        if (_cache &&
            !(_options & YYWebImageOptionUseNSURLCache) &&
            !(_options & YYWebImageOptionRefreshImageCache)) {
            UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
            if (image) {//取到了圖片
                [_lock lock];
            
                if (![self isCancelled]) {//沒有取消,
                    //如果已經(jīng)完成,把圖片,圖片url,緩存類型,下載結果通過block傳遞回去
                    if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
                }
                //調(diào)用結束方法
                [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) {
                        //先把圖片再存進內(nèi)存緩存
                        [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
                        //在網(wǎng)絡線程調(diào)用_didReceiveImageFromDiskCache方法
                        [self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
                    } else {
                    //沒有取到圖片,就開始在網(wǎng)絡線程,開始請求
                        [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
                    }
                });
                return;
            }
        }
    }
    //在網(wǎng)絡線程立刻開始請求
    [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}

//這個方法會確保跑在網(wǎng)絡請求線程
- (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以及合適的參數(shù)傳遞給完成的回調(diào),
            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內(nèi)部delegate是weak/strong還是assign屬性,這樣做可以保證任何情況下都不會出錯
            _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[_YYWebImageWeakProxy proxyWithTarget:self]];
            //url不可用,并且模式是YYWebImageOptionShowNetworkActivity,給網(wǎng)絡請求數(shù)量+1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager incrementNetworkActivityCount];
            }
        }
        //結果出來了,解鎖
        [_lock unlock];
    }
}

取消的方法實現(xiàn)

/**
 *  跑在網(wǎng)絡線程上,被另外一個"cancel方法調(diào)用"
 */
- (void)_cancelOperation {
    @autoreleasepool {
        if (_connection) {
            
            //url不可用并且模式是YYWebImageOptionShowNetworkActivity,請求數(shù)量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
        }
        //取消操作并置空
        [_connection cancel];
        _connection = nil;
        //如果實現(xiàn)了完成的block,把相應參數(shù)與狀態(tài)傳遞回去
        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) {
                //如果有完成的回調(diào),則傳遞回去,標記為磁盤緩存
                if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil);
                [self _finish];
            } else {
                [self _startRequest:nil];
            }
        }
        [_lock unlock];
    }
}

/**
 *  從網(wǎng)絡下載的圖片
 *
 *  @param image <#image description#>
 */
- (void)_didReceiveImageFromWeb:(UIImage *)image {
    @autoreleasepool {
        [_lock lock];
        if (![self isCancelled]) {
            if (_cache) {
                //有圖片 或者 模式是刷新緩存的
                if (image || (_options & YYWebImageOptionRefreshImageCache)) {
                    NSData *data = _data;
                    //開一個異步線程,把圖片同時存進磁盤與內(nèi)存緩存
                    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來實現(xiàn)的,自然需要在其代理方法里面做接收數(shù)據(jù)的操作,以下是其代理方法的實現(xiàn),有點長

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

//即將發(fā)送請求驗證,驗證授權的一大堆亂七八糟東西,暫時不去管
- (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;
    }
}

//請求已經(jīng)收到相應
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    @autoreleasepool {
        NSError *error = nil;
        //先判斷是不是NSHTTPURLResponse相應類,是的話,先把狀態(tài)碼記錄下來,如果出錯,記錄一個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了,取消連接,調(diào)用連接失敗的方法同時把error傳遞過去
        if (error) {
            [_connection cancel];
            [self connection:_connection didFailWithError:error];
        } else {
            //通過length判斷有內(nèi)容,賦值
            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];
            }
        }
    }
}

//收到數(shù)據(jù)回調(diào)
- (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 ----------------------------*/
        //根據(jù)模式判斷是否需要返回進度以及是否需要漸進顯示
        BOOL progressive = (_options & YYWebImageOptionProgressive) > 0;
        BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0;
        //如果沒有實現(xiàn)了完成block,或者沒有任何進度,直接返回
        if (!_completion || !(progressive || progressiveBlur)) return;
        //如果data長度小于一個字節(jié),直接返回
        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];
        }
        //解碼器更新數(shù)據(jù)
        [_progressiveDecoder updateData:_data final:NO];
        //如果調(diào)用取消方法,直接返回
        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]) {
                    //沒有取消,把數(shù)據(jù)傳遞給完成block,
                    _completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
                    //給_lastProgressiveDecodeTimestamp賦值
                    _lastProgressiveDecodeTimestamp = now;
                }
                [_lock unlock];
            }
            return;
        } else {
            //解碼之后發(fā)現(xiàn)是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;
                }
                //縮放長度為 接收到數(shù)據(jù)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];
            }
        }
    }
}

//連接已經(jīng)結束加載
- (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.
                 */
                //如果是動圖,保存原始圖片數(shù)據(jù)到磁盤緩存,如果圖片不是PNG或者JPEG格式,把圖片轉(zhuǎn)碼成PNG或者JPEG格式,此舉是為了得到更好的解碼表現(xiàn)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,清空數(shù)據(jù),給緩存轉(zhuǎn)碼
                                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) {
                    //傳遞回調(diào)
                    UIImage *newImage = self.transform(image, self.request.URL);
                    //圖片錯了,清空
                    if (newImage != image) {
                        self.data = nil;
                    }
                    //正確GET
                    image = newImage;
                    if ([self isCancelled]) return;
                }
                
                //調(diào)用_didReceiveImageFromWeb方法表明從網(wǎng)絡上下載的圖片
                [self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
            });
            //如果圖片URL不可用,并且模式是YYWebImageOptionShowNetworkActivity,網(wǎng)絡請求數(shù)量-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模式,網(wǎng)絡請求數(shù)量-1
            if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
                [YYWebImageManager decrementNetworkActivityCount];
            }
            //手動調(diào)一下結束方法
            [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的狀態(tài)值方法,主要看start跟cancel

/**
 *  開始這個NSOperation
 */
- (void)start {
    @autoreleasepool {
        [_lock lock];
        self.started = YES;//賦值開始標記為YES
        if ([self isCancelled]) {
            //如果這時候被取消了,調(diào)用取消方法
            [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
            self.finished = YES;//標記結束位YES
            //或者如果在準備開始,并且沒有結束,并且沒有運行中,執(zhí)行以下操作
        } 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 {
                //設置正在執(zhí)行為YES
                self.executing = YES;
                //調(diào)用開始方法
                [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];
    //先檢查是不是取消了,沒有取消調(diào)用父類取消,設置自己取消為YES
    if (![self isCancelled]) {
        [super cancel];
        self.cancelled = YES;
        //如果正在執(zhí)行中,設置執(zhí)行中為NO,調(diào)用取消
        if ([self isExecuting]) {
            self.executing = NO;
            [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
        }
        //如果已經(jīng)開始,直接標記結束,不做其他處理
        if (self.started) {
            self.finished = YES;
        }
    }
    [_lock unlock];
}

PS:
YYWebImage源碼地址
我fork下來添加注釋的版本github地址

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瑟俭,一起剝皮案震驚了整個濱河市黑忱,隨后出現(xiàn)的幾起案子里覆,更是在濱河造成了極大的恐慌,老刑警劉巖漫试,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡焙蹭,警方通過查閱死者的電腦和手機位仁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門柑贞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人聂抢,你說我怎么就攤上這事钧嘶。” “怎么了琳疏?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵有决,是天一觀的道長。 經(jīng)常有香客問我空盼,道長书幕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任揽趾,我火速辦了婚禮台汇,結果婚禮上,老公的妹妹穿的比我還像新娘篱瞎。我一直安慰自己苟呐,他們只是感情好,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布俐筋。 她就那樣靜靜地躺著牵素,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澄者。 梳的紋絲不亂的頭發(fā)上两波,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機與錄音闷哆,去河邊找鬼腰奋。 笑死,一個胖子當著我的面吹牛抱怔,可吹牛的內(nèi)容都是我干的劣坊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屈留,長吁一口氣:“原來是場噩夢啊……” “哼局冰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灌危,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤康二,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后勇蝙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忍疾,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡舶替,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年倚舀,在試婚紗的時候發(fā)現(xiàn)自己被綠了辖试。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夕土,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布瘟判,位于F島的核電站怨绣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拷获。R本人自食惡果不足惜篮撑,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刀诬。 院中可真熱鬧咽扇,春花似錦、人聲如沸陕壹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糠馆。三九已至嘶伟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間又碌,已是汗流浹背九昧。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毕匀,地道東北人铸鹰。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像皂岔,于是被迫代替她去往敵國和親蹋笼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容