123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 |
- //
- // YYWebImageOperation.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/2/15.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYWebImageOperation.h"
- #import "UIApplication+YYAdd.h"
- #import "YYImage.h"
- #import "YYWeakProxy.h"
- #import "UIImage+YYAdd.h"
- #import <ImageIO/ImageIO.h>
- #import "YYKitMacro.h"
- #if __has_include("YYDispatchQueuePool.h")
- #import "YYDispatchQueuePool.h"
- #else
- #import <libkern/OSAtomic.h>
- #endif
- #define MIN_PROGRESSIVE_TIME_INTERVAL 0.2
- #define MIN_PROGRESSIVE_BLUR_TIME_INTERVAL 0.4
- /// Returns YES if the right-bottom pixel is filled.
- static BOOL YYCGImageLastPixelFilled(CGImageRef image) {
- if (!image) return NO;
- size_t width = CGImageGetWidth(image);
- size_t height = CGImageGetHeight(image);
- if (width == 0 || height == 0) return NO;
- CGContextRef ctx = CGBitmapContextCreate(NULL, 1, 1, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault);
- if (!ctx) return NO;
- CGContextDrawImage(ctx, CGRectMake( -(int)width + 1, 0, width, height), image);
- uint8_t *bytes = CGBitmapContextGetData(ctx);
- BOOL isAlpha = bytes && bytes[0] == 0;
- CFRelease(ctx);
- return !isAlpha;
- }
- /// Returns JPEG SOS (Start Of Scan) Marker
- static NSData *JPEGSOSMarker() {
- // "Start Of Scan" Marker
- static NSData *marker = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- uint8_t bytes[2] = {0xFF, 0xDA};
- marker = [NSData dataWithBytes:bytes length:2];
- });
- return marker;
- }
- static NSMutableSet *URLBlacklist;
- static dispatch_semaphore_t URLBlacklistLock;
- static void URLBlacklistInit() {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- URLBlacklist = [NSMutableSet new];
- URLBlacklistLock = dispatch_semaphore_create(1);
- });
- }
- static BOOL URLBlackListContains(NSURL *url) {
- if (!url || url == (id)[NSNull null]) return NO;
- URLBlacklistInit();
- dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
- BOOL contains = [URLBlacklist containsObject:url];
- dispatch_semaphore_signal(URLBlacklistLock);
- return contains;
- }
- static void URLInBlackListAdd(NSURL *url) {
- if (!url || url == (id)[NSNull null]) return;
- URLBlacklistInit();
- dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
- [URLBlacklist addObject:url];
- dispatch_semaphore_signal(URLBlacklistLock);
- }
- @interface YYWebImageOperation() <NSURLConnectionDelegate>
- @property (readwrite, getter=isExecuting) BOOL executing;
- @property (readwrite, getter=isFinished) BOOL finished;
- @property (readwrite, getter=isCancelled) BOOL cancelled;
- @property (readwrite, getter=isStarted) BOOL started;
- @property (nonatomic, strong) NSRecursiveLock *lock;
- @property (nonatomic, strong) NSURLConnection *connection;
- @property (nonatomic, strong) NSMutableData *data;
- @property (nonatomic, assign) NSInteger expectedSize;
- @property (nonatomic, assign) UIBackgroundTaskIdentifier taskID;
- @property (nonatomic, assign) NSTimeInterval lastProgressiveDecodeTimestamp;
- @property (nonatomic, strong) YYImageDecoder *progressiveDecoder;
- @property (nonatomic, assign) BOOL progressiveIgnored;
- @property (nonatomic, assign) BOOL progressiveDetected;
- @property (nonatomic, assign) NSUInteger progressiveScanedLength;
- @property (nonatomic, assign) NSUInteger progressiveDisplayCount;
- @property (nonatomic, copy) YYWebImageProgressBlock progress;
- @property (nonatomic, copy) YYWebImageTransformBlock transform;
- @property (nonatomic, copy) YYWebImageCompletionBlock completion;
- @end
- @implementation YYWebImageOperation
- @synthesize executing = _executing;
- @synthesize finished = _finished;
- @synthesize cancelled = _cancelled;
- /// Network thread entry point.
- + (void)_networkThreadMain:(id)object {
- @autoreleasepool {
- [[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"];
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
- [runLoop run];
- }
- }
- /// Global image request network thread, used by NSURLConnection delegate.
- + (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;
- 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.yykit.decode", attr);
- }
- } else {
- for (NSUInteger i = 0; i < queueCount; i++) {
- queues[i] = dispatch_queue_create("com.ibireme.yykit.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
- }
- - (instancetype)init {
- @throw [NSException exceptionWithName:@"YYWebImageOperation init error" reason:@"YYWebImageOperation must be initialized with a request. Use the designated initializer to init." userInfo:nil];
- return [self initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] options:0 cache:nil cacheKey:nil progress:nil transform:nil completion:nil];
- }
- - (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;
- _cacheKey = cacheKey ? cacheKey : request.URL.absoluteString;
- _shouldUseCredentialStorage = YES;
- _progress = progress;
- _transform = transform;
- _completion = completion;
- _executing = NO;
- _finished = NO;
- _cancelled = NO;
- _taskID = UIBackgroundTaskInvalid;
- _lock = [NSRecursiveLock new];
- return self;
- }
- - (void)dealloc {
- [_lock lock];
- if (_taskID != UIBackgroundTaskInvalid) {
- [[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
- _taskID = UIBackgroundTaskInvalid;
- }
- if ([self isExecuting]) {
- self.cancelled = YES;
- self.finished = YES;
- if (_connection) {
- [_connection cancel];
- if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
- [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
- }
- }
- if (_completion) {
- @autoreleasepool {
- _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
- }
- }
- }
- [_lock unlock];
- }
- - (void)_endBackgroundTask {
- [_lock lock];
- if (_taskID != UIBackgroundTaskInvalid) {
- [[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
- _taskID = UIBackgroundTaskInvalid;
- }
- [_lock unlock];
- }
- #pragma mark - Runs in operation thread
- - (void)_finish {
- self.executing = NO;
- self.finished = YES;
- [self _endBackgroundTask];
- }
- // runs on network thread
- - (void)_startOperation {
- if ([self isCancelled]) return;
- @autoreleasepool {
- // get image from cache
- if (_cache &&
- !(_options & YYWebImageOptionUseNSURLCache) &&
- !(_options & YYWebImageOptionRefreshImageCache)) {
- UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
- if (image) {
- [_lock lock];
- if (![self isCancelled]) {
- if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
- }
- [self _finish];
- [_lock unlock];
- return;
- }
- if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
- __weak typeof(self) _self = self;
- dispatch_async([self.class _imageQueue], ^{
- __strong typeof(_self) self = _self;
- if (!self || [self isCancelled]) return;
- UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
- if (image) {
- [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
- [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];
- }
- // runs on network thread
- - (void)_startRequest:(id)object {
- if ([self isCancelled]) return;
- @autoreleasepool {
- if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
- NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
- [_lock lock];
- if (![self isCancelled]) {
- if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
- }
- [self _finish];
- [_lock unlock];
- return;
- }
-
- if (_request.URL.isFileURL) {
- NSArray *keys = @[NSURLFileSizeKey];
- NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil];
- NSNumber *fileSize = attr[NSURLFileSizeKey];
- _expectedSize = (fileSize != nil) ? fileSize.unsignedIntegerValue : -1;
- }
-
- // request image from web
- [_lock lock];
- if (![self isCancelled]) {
- _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[YYWeakProxy proxyWithTarget:self]];
- if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
- [[UIApplication sharedExtensionApplication] incrementNetworkActivityCount];
- }
- }
- [_lock unlock];
- }
- }
- // runs on network thread, called from outer "cancel"
- - (void)_cancelOperation {
- @autoreleasepool {
- if (_connection) {
- if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
- [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
- }
- }
- [_connection cancel];
- _connection = nil;
- if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
- [self _endBackgroundTask];
- }
- }
- // 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];
- }
- }
- - (void)_didReceiveImageFromWeb:(UIImage *)image {
- @autoreleasepool {
- [_lock lock];
- if (![self isCancelled]) {
- if (_cache) {
- if (image || (_options & YYWebImageOptionRefreshImageCache)) {
- NSData *data = _data;
- dispatch_async([YYWebImageOperation _imageQueue], ^{
- YYImageCacheType cacheType = (_options & YYWebImageOptionIgnoreDiskCache) ? YYImageCacheTypeMemory : YYImageCacheTypeAll;
- [_cache setImage:image imageData:data forKey:_cacheKey withType:cacheType];
- });
- }
- }
- _data = nil;
- NSError *error = nil;
- if (!image) {
- error = [NSError errorWithDomain:@"com.ibireme.yykit.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
- if (_options & YYWebImageOptionIgnoreFailedURL) {
- if (URLBlackListContains(_request.URL)) {
- error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
- } else {
- URLInBlackListAdd(_request.URL);
- }
- }
- }
- if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
- [self _finish];
- }
- [_lock unlock];
- }
- }
- #pragma mark - NSURLConnectionDelegate runs in operation thread
- - (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;
- if (_options & YYWebImageOptionUseNSURLCache) {
- return cachedResponse;
- } else {
- // ignore NSURLCache
- return nil;
- }
- }
- - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
- @autoreleasepool {
- NSError *error = nil;
- 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];
- }
- }
- if (error) {
- [_connection cancel];
- [self connection:_connection didFailWithError:error];
- } else {
- if (response.expectedContentLength) {
- _expectedSize = (NSInteger)response.expectedContentLength;
- if (_expectedSize < 0) _expectedSize = -1;
- }
- _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;
-
- 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;
- if (!_completion || !(progressive || progressiveBlur)) return;
- if (data.length <= 16) return;
- 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;
- }
- 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]) {
- _completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
- _lastProgressiveDecodeTimestamp = now;
- }
- [_lock unlock];
- }
- return;
- } else {
- 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;
- }
-
- NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4;
- 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) {
- 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 imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil];
-
- if (image) {
- [_lock lock];
- if (![self isCancelled]) {
- _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;
-
- BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0;
- BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0;
- UIImage *image;
- BOOL hasAnimation = NO;
- if (allowAnimation) {
- image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale];
- if (shouldDecode) image = [image 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.
- */
- 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) {
- 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;
-
- if (self.transform && image) {
- UIImage *newImage = self.transform(image, self.request.URL);
- if (newImage != image) {
- self.data = nil;
- }
- image = newImage;
- if ([self isCancelled]) return;
- }
-
- [self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
- });
- if (![self.request.URL isFileURL] && (self.options & YYWebImageOptionShowNetworkActivity)) {
- [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
- }
- }
- [_lock unlock];
- }
- }
- - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
- @autoreleasepool {
- [_lock lock];
- if (![self isCancelled]) {
- if (_completion) {
- _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
- }
- _connection = nil;
- _data = nil;
- if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
- [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
- }
- [self _finish];
-
- if (_options & YYWebImageOptionIgnoreFailedURL) {
- if (error.code != NSURLErrorNotConnectedToInternet &&
- error.code != NSURLErrorCancelled &&
- error.code != NSURLErrorTimedOut &&
- error.code != NSURLErrorUserCancelledAuthentication &&
- error.code != NSURLErrorNetworkConnectionLost) {
- URLInBlackListAdd(_request.URL);
- }
- }
- }
- [_lock unlock];
- }
- }
- #pragma mark - Override NSOperation
- - (void)start {
- @autoreleasepool {
- [_lock lock];
- self.started = YES;
- if ([self isCancelled]) {
- [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
- self.finished = YES;
- } else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
- if (!_request) {
- self.finished = YES;
- if (_completion) {
- NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
- _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
- }
- } else {
- self.executing = YES;
- [self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
- if ((_options & YYWebImageOptionAllowBackgroundTask) && ![UIApplication isAppExtension]) {
- __weak __typeof__ (self) _self = self;
- if (_taskID == UIBackgroundTaskInvalid) {
- _taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{
- __strong __typeof (_self) self = _self;
- if (self) {
- [self cancel];
- self.finished = YES;
- }
- }];
- }
- }
- }
- }
- [_lock unlock];
- }
- }
- - (void)cancel {
- [_lock lock];
- if (![self isCancelled]) {
- [super cancel];
- self.cancelled = YES;
- 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];
- }
- - (void)setExecuting:(BOOL)executing {
- [_lock lock];
- if (_executing != executing) {
- [self willChangeValueForKey:@"isExecuting"];
- _executing = executing;
- [self didChangeValueForKey:@"isExecuting"];
- }
- [_lock unlock];
- }
- - (BOOL)isExecuting {
- [_lock lock];
- BOOL executing = _executing;
- [_lock unlock];
- return executing;
- }
- - (void)setFinished:(BOOL)finished {
- [_lock lock];
- if (_finished != finished) {
- [self willChangeValueForKey:@"isFinished"];
- _finished = finished;
- [self didChangeValueForKey:@"isFinished"];
- }
- [_lock unlock];
- }
- - (BOOL)isFinished {
- [_lock lock];
- BOOL finished = _finished;
- [_lock unlock];
- return finished;
- }
- - (void)setCancelled:(BOOL)cancelled {
- [_lock lock];
- if (_cancelled != cancelled) {
- [self willChangeValueForKey:@"isCancelled"];
- _cancelled = cancelled;
- [self didChangeValueForKey:@"isCancelled"];
- }
- [_lock unlock];
- }
- - (BOOL)isCancelled {
- [_lock lock];
- BOOL cancelled = _cancelled;
- [_lock unlock];
- return cancelled;
- }
- - (BOOL)isConcurrent {
- return YES;
- }
- - (BOOL)isAsynchronous {
- return YES;
- }
- + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
- if ([key isEqualToString:@"isExecuting"] ||
- [key isEqualToString:@"isFinished"] ||
- [key isEqualToString:@"isCancelled"]) {
- return NO;
- }
- return [super automaticallyNotifiesObserversForKey:key];
- }
- - (NSString *)description {
- NSMutableString *string = [NSMutableString stringWithFormat:@"<%@: %p ",self.class, self];
- [string appendFormat:@" executing:%@", [self isExecuting] ? @"YES" : @"NO"];
- [string appendFormat:@" finished:%@", [self isFinished] ? @"YES" : @"NO"];
- [string appendFormat:@" cancelled:%@", [self isCancelled] ? @"YES" : @"NO"];
- [string appendString:@">"];
- return string;
- }
- @end
|