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)進行正確的處理,由于NSOperation
的isCancelled
方法并不是能夠?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];
}