diff --git a/YYKit/Base/UIKit/UIDevice+YYAdd.m b/YYKit/Base/UIKit/UIDevice+YYAdd.m index 296b4e1e..92adaa51 100644 --- a/YYKit/Base/UIKit/UIDevice+YYAdd.m +++ b/YYKit/Base/UIKit/UIDevice+YYAdd.m @@ -53,11 +53,11 @@ - (BOOL)isSimulator { - (BOOL)isJailbroken { if ([self isSimulator]) return NO; // Dont't check simulator - + // iOS9 URL Scheme query changed ... // NSURL *cydiaURL = [NSURL URLWithString:@"cydia://package"]; // if ([[UIApplication sharedApplication] canOpenURL:cydiaURL]) return YES; - + NSArray *paths = @[@"/Applications/Cydia.app", @"/private/var/lib/apt/", @"/private/var/lib/cydia", @@ -65,19 +65,19 @@ - (BOOL)isJailbroken { for (NSString *path in paths) { if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return YES; } - + FILE *bash = fopen("/bin/bash", "r"); if (bash != NULL) { fclose(bash); return YES; } - + NSString *path = [NSString stringWithFormat:@"/private/%@", [NSString stringWithUUID]]; if ([@"test" writeToFile : path atomically : YES encoding : NSUTF8StringEncoding error : NULL]) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; return YES; } - + return NO; } @@ -109,7 +109,7 @@ - (NSString *)ipAddressWithIfaName:(NSString *)name { address = [NSString stringWithUTF8String:str]; } } break; - + case AF_INET6: { // IPv6 char str[INET6_ADDRSTRLEN] = {0}; inet_ntop(family, &(((struct sockaddr_in6 *)addr->ifa_addr)->sin6_addr), str, sizeof(str)); @@ -117,7 +117,7 @@ - (NSString *)ipAddressWithIfaName:(NSString *)name { address = [NSString stringWithUTF8String:str]; } } - + default: break; } if (address) break; @@ -179,7 +179,7 @@ static yy_net_interface_counter yy_get_net_interface_counter() { sharedOutCounters = [NSMutableDictionary new]; lock = dispatch_semaphore_create(1); }); - + yy_net_interface_counter counter = {0}; struct ifaddrs *addrs; const struct ifaddrs *cursor; @@ -194,11 +194,11 @@ static yy_net_interface_counter yy_get_net_interface_counter() { uint64_t counter_in = ((NSNumber *)sharedInCounters[name]).unsignedLongLongValue; counter_in = yy_net_counter_add(counter_in, data->ifi_ibytes); sharedInCounters[name] = @(counter_in); - + uint64_t counter_out = ((NSNumber *)sharedOutCounters[name]).unsignedLongLongValue; counter_out = yy_net_counter_add(counter_out, data->ifi_obytes); sharedOutCounters[name] = @(counter_out); - + if ([name hasPrefix:@"en"]) { counter.en_in += counter_in; counter.en_out += counter_out; @@ -216,7 +216,7 @@ static yy_net_interface_counter yy_get_net_interface_counter() { dispatch_semaphore_signal(lock); freeifaddrs(addrs); } - + return counter; } @@ -252,14 +252,14 @@ - (NSString *)machineModelName { @"Watch2,4" : @"Apple Watch Series 2 42mm", @"Watch2,6" : @"Apple Watch Series 1 38mm", @"Watch1,7" : @"Apple Watch Series 1 42mm", - + @"iPod1,1" : @"iPod touch 1", @"iPod2,1" : @"iPod touch 2", @"iPod3,1" : @"iPod touch 3", @"iPod4,1" : @"iPod touch 4", @"iPod5,1" : @"iPod touch 5", @"iPod7,1" : @"iPod touch 6", - + @"iPhone1,1" : @"iPhone 1G", @"iPhone1,2" : @"iPhone 3G", @"iPhone2,1" : @"iPhone 3GS", @@ -282,7 +282,17 @@ - (NSString *)machineModelName { @"iPhone9,2" : @"iPhone 7 Plus", @"iPhone9,3" : @"iPhone 7", @"iPhone9,4" : @"iPhone 7 Plus", - + @"iPhone10,1" : @"iPhone 8", + @"iPhone10,4" : @"iPhone 8", + @"iPhone10,2" : @"iPhone 8 Plus", + @"iPhone10,5" : @"iPhone 8 Plus", + @"iPhone10,3" : @"iPhone X", + @"iPhone10,6" : @"iPhone X", + @"iPhone11,2" : @"iPhone XS", + @"iPhone11,4" : @"iPhone XS Max", + @"iPhone11,6" : @"iPhone XS Max China", + @"iPhone11,8" : @"iPhone XR", + @"iPad1,1" : @"iPad 1", @"iPad2,1" : @"iPad 2 (WiFi)", @"iPad2,2" : @"iPad 2 (GSM)", @@ -314,12 +324,18 @@ - (NSString *)machineModelName { @"iPad6,4" : @"iPad Pro (9.7 inch)", @"iPad6,7" : @"iPad Pro (12.9 inch)", @"iPad6,8" : @"iPad Pro (12.9 inch)", - + @"iPad6,11" : @"iPad 5 (WiFi)", + @"iPad6,12" : @"iPad 5 (Wifi + Cellular)", + @"iPad7,1" : @"iPad Pro 2 12.9 (Wifi)", + @"iPad7,2" : @"iPad Pro 2 12.9 (Wifi + Cellular)", + @"iPad7,3" : @"iPad PRO 10.5 (Wifi)", + @"iPad7,4" : @"iPad PRO 10.5 (Wifi + Cellular)", + @"AppleTV2,1" : @"Apple TV 2", @"AppleTV3,1" : @"Apple TV 3", @"AppleTV3,2" : @"Apple TV 3", @"AppleTV5,3" : @"Apple TV 4", - + @"i386" : @"Simulator x86", @"x86_64" : @"Simulator x64", }; @@ -373,7 +389,7 @@ - (int64_t)memoryUsed { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -387,7 +403,7 @@ - (int64_t)memoryFree { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -401,7 +417,7 @@ - (int64_t)memoryActive { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -415,7 +431,7 @@ - (int64_t)memoryInactive { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -429,7 +445,7 @@ - (int64_t)memoryWired { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -443,7 +459,7 @@ - (int64_t)memoryPurgable { vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kern; - + kern = host_page_size(host_port, &page_size); if (kern != KERN_SUCCESS) return -1; kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); @@ -470,20 +486,20 @@ - (NSArray *)cpuUsagePerProcessor { mach_msg_type_number_t _numCPUInfo, _numPrevCPUInfo = 0; unsigned _numCPUs; NSLock *_cpuUsageLock; - + int _mib[2U] = { CTL_HW, HW_NCPU }; size_t _sizeOfNumCPUs = sizeof(_numCPUs); int _status = sysctl(_mib, 2U, &_numCPUs, &_sizeOfNumCPUs, NULL, 0U); if (_status) _numCPUs = 1; - + _cpuUsageLock = [[NSLock alloc] init]; - + natural_t _numCPUsU = 0U; kern_return_t err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &_numCPUsU, &_cpuInfo, &_numCPUInfo); if (err == KERN_SUCCESS) { [_cpuUsageLock lock]; - + NSMutableArray *cpus = [NSMutableArray new]; for (unsigned i = 0U; i < _numCPUs; ++i) { Float32 _inUse, _total; @@ -500,7 +516,7 @@ - (NSArray *)cpuUsagePerProcessor { } [cpus addObject:@(_inUse / _total)]; } - + [_cpuUsageLock unlock]; if (_prevCPUInfo) { size_t prevCpuInfoSize = sizeof(integer_t) * _numPrevCPUInfo; diff --git a/YYKit/Image/YYWebImageManager.m b/YYKit/Image/YYWebImageManager.m index 5c0bc408..a13bdd44 100644 --- a/YYKit/Image/YYWebImageManager.m +++ b/YYKit/Image/YYWebImageManager.m @@ -14,6 +14,13 @@ #import "YYWebImageOperation.h" #import "YYImageCoder.h" +@interface YYWebImageManager () + +// The session in which data tasks will run +@property (strong, nonatomic) NSURLSession *session; + +@end + @implementation YYWebImageManager + (instancetype)sharedManager { @@ -46,9 +53,29 @@ - (instancetype)initWithCache:(YYImageCache *)cache queue:(NSOperationQueue *)qu } else { _headers = @{ @"Accept" : @"image/*;q=0.8" }; } + + [self createNewSessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; return self; } +- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration { + + if (self.session) { + [self.session invalidateAndCancel]; + } + + sessionConfiguration.timeoutIntervalForRequest = _timeout; + + /** + * Create the session for this task + * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate + * method calls and completion handler calls. + */ + self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration + delegate:self + delegateQueue:nil]; +} + - (YYWebImageOperation *)requestImageWithURL:(NSURL *)url options:(YYWebImageOptions)options progress:(YYWebImageProgressBlock)progress @@ -67,6 +94,7 @@ - (YYWebImageOperation *)requestImageWithURL:(NSURL *)url options:options cache:_cache cacheKey:[self cacheKeyForURL:url] + session:_session progress:progress transform:transform ? transform : _sharedTransformBlock completion:completion]; @@ -95,4 +123,94 @@ - (NSString *)cacheKeyForURL:(NSURL *)url { return _cacheKeyFilter ? _cacheKeyFilter(url) : url.absoluteString; } +- (YYWebImageOperation *)operationWithTask:(NSURLSessionTask *)task { + YYWebImageOperation *returnOperation = nil; + for (YYWebImageOperation *operation in self.queue.operations) { + if (operation.dataTask.taskIdentifier == task.taskIdentifier) { + returnOperation = operation; + break; + } + } + return returnOperation; +} + +#pragma mark NSURLSessionDataDelegate + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:dataTask]; + if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) { + [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; + } else { + if (completionHandler) { + completionHandler(NSURLSessionResponseAllow); + } + } +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:dataTask]; + if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) { + [dataOperation URLSession:session dataTask:dataTask didReceiveData:data]; + } +} + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)proposedResponse + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:dataTask]; + if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) { + [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; + } else { + if (completionHandler) { + completionHandler(proposedResponse); + } + } +} + +#pragma mark NSURLSessionTaskDelegate + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:task]; + if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { + [dataOperation URLSession:session task:task didCompleteWithError:error]; + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:task]; + if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) { + [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler]; + } else { + if (completionHandler) { + completionHandler(request); + } + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + + // Identify the operation that runs this task and pass it the delegate method + YYWebImageOperation *dataOperation = [self operationWithTask:task]; + if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) { + [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; + } else { + if (completionHandler) { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + } + } +} @end diff --git a/YYKit/Image/YYWebImageOperation.h b/YYKit/Image/YYWebImageOperation.h index eb24fc56..7ca5a989 100644 --- a/YYKit/Image/YYWebImageOperation.h +++ b/YYKit/Image/YYWebImageOperation.h @@ -37,13 +37,15 @@ NS_ASSUME_NONNULL_BEGIN 4. Put the image to cache and return it with `completion` block. */ -@interface YYWebImageOperation : NSOperation +@interface YYWebImageOperation : NSOperation @property (nonatomic, strong, readonly) NSURLRequest *request; ///< The image URL request. -@property (nullable, nonatomic, strong, readonly) NSURLResponse *response; ///< The response for request. +@property (nullable, nonatomic, strong) NSURLResponse *response; ///< The response for request. @property (nullable, nonatomic, strong, readonly) YYImageCache *cache; ///< The image cache. @property (nonatomic, strong, readonly) NSString *cacheKey; ///< The image cache key. @property (nonatomic, readonly) YYWebImageOptions options; ///< The operation's option. +@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; ////< The operation's task + /** Whether the URL connection should consult the credential storage for authenticating @@ -85,6 +87,7 @@ NS_ASSUME_NONNULL_BEGIN options:(YYWebImageOptions)options cache:(nullable YYImageCache *)cache cacheKey:(nullable NSString *)cacheKey + session:(nullable NSURLSession*)session progress:(nullable YYWebImageProgressBlock)progress transform:(nullable YYWebImageTransformBlock)transform completion:(nullable YYWebImageCompletionBlock)completion NS_DESIGNATED_INITIALIZER; diff --git a/YYKit/Image/YYWebImageOperation.m b/YYKit/Image/YYWebImageOperation.m index 2b438e0f..a59e7860 100644 --- a/YYKit/Image/YYWebImageOperation.m +++ b/YYKit/Image/YYWebImageOperation.m @@ -89,7 +89,7 @@ @interface YYWebImageOperation() @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) NSURLSession* session; @property (nonatomic, strong) NSMutableData *data; @property (nonatomic, assign) NSInteger expectedSize; @property (nonatomic, assign) UIBackgroundTaskIdentifier taskID; @@ -100,6 +100,7 @@ @interface YYWebImageOperation() @property (nonatomic, assign) BOOL progressiveDetected; @property (nonatomic, assign) NSUInteger progressiveScanedLength; @property (nonatomic, assign) NSUInteger progressiveDisplayCount; +@property (copy, nonatomic, nullable) NSData *cachedData; @property (nonatomic, copy) YYWebImageProgressBlock progress; @property (nonatomic, copy) YYWebImageTransformBlock transform; @@ -170,13 +171,21 @@ + (dispatch_queue_t)_imageQueue { - (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]; + return [self initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] + options:0 + cache:nil + cacheKey:nil + session:nil + progress:nil + transform:nil + completion:nil]; } - (instancetype)initWithRequest:(NSURLRequest *)request options:(YYWebImageOptions)options cache:(YYImageCache *)cache cacheKey:(NSString *)cacheKey + session:(NSURLSession*)session progress:(YYWebImageProgressBlock)progress transform:(YYWebImageTransformBlock)transform completion:(YYWebImageCompletionBlock)completion { @@ -194,6 +203,21 @@ - (instancetype)initWithRequest:(NSURLRequest *)request _executing = NO; _finished = NO; _cancelled = NO; + if (!session) { + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + sessionConfig.timeoutIntervalForRequest = 15; + + /** + * Create the session for this task + * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate + * method calls and completion handler calls. + */ + session = [NSURLSession sessionWithConfiguration:sessionConfig + delegate:self + delegateQueue:nil]; + + } + _session = session; _taskID = UIBackgroundTaskInvalid; _lock = [NSRecursiveLock new]; return self; @@ -208,8 +232,8 @@ - (void)dealloc { if ([self isExecuting]) { self.cancelled = YES; self.finished = YES; - if (_connection) { - [_connection cancel]; + if (_dataTask) { + [_dataTask cancel]; if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount]; } @@ -303,10 +327,33 @@ - (void)_startRequest:(id)object { // request image from web [_lock lock]; if (![self isCancelled]) { - _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[YYWeakProxy proxyWithTarget:self]]; + _dataTask = [self.session dataTaskWithRequest:_request]; + [self setExecuting:YES]; if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { [[UIApplication sharedExtensionApplication] incrementNetworkActivityCount]; } + + if (_dataTask) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + if ([self.dataTask respondsToSelector:@selector(setPriority:)]) { + self.dataTask.priority = NSURLSessionTaskPriorityHigh; + } +#pragma clang diagnostic pop + [self.dataTask resume]; + if (_progress) { + _progress(0,0xFFFFFFFF-1); + } + } else { + _completion(nil, + _request.URL, + YYWebImageFromNone, + YYWebImageStageFinished, + [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]); + [self setExecuting:NO]; + [self setFinished:YES]; + } } [_lock unlock]; } @@ -315,13 +362,13 @@ - (void)_startRequest:(id)object { // runs on network thread, called from outer "cancel" - (void)_cancelOperation { @autoreleasepool { - if (_connection) { + if (_dataTask) { if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount]; } } - [_connection cancel]; - _connection = nil; + [_dataTask cancel]; + _dataTask = nil; if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil); [self _endBackgroundTask]; } @@ -377,82 +424,55 @@ - (void)_didReceiveImageFromWeb:(UIImage *)image { } } -#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]; - } - } +#pragma mark - NSURLSessionTaskDelegate + +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask +didReceiveResponse:(NSURLResponse *)response + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler +{ + NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; + NSInteger expected = (NSInteger)response.expectedContentLength; + expected = expected > 0 ? expected : 0; + self.expectedSize = expected; + self.response = response; + NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200; + BOOL valid = statusCode < 400; + //'304 Not Modified' is an exceptional one. It should be treated as cancelled if no cache data + //URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check + if (statusCode == 304 && !self.cachedData) { + valid = NO; } -} - -- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { - if (!cachedResponse) return cachedResponse; - if (_options & YYWebImageOptionUseNSURLCache) { - return cachedResponse; + + if (valid) { + if (_progress) { + _progress(0,expected); + } } else { - // ignore NSURLCache - return nil; + // Status code invalid and marked as cancelled. Do not call `[self.dataTask cancel]` which may mass up URLSession life cycle + disposition = NSURLSessionResponseCancel; } -} + -- (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]; - } - } + if (completionHandler) { + completionHandler(disposition); } } -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data +{ @autoreleasepool { [_lock lock]; BOOL canceled = [self isCancelled]; [_lock unlock]; if (canceled) return; - - if (data) [_data appendData:data]; + + if (!self.data) { + self.data = [[NSMutableData alloc] initWithCapacity:self.expectedSize]; + } + [_data appendData:data]; if (_progress) { [_lock lock]; if (![self isCancelled]) { @@ -571,99 +591,150 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { } } -- (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; +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + willCacheResponse:(NSCachedURLResponse *)cachedResponse + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler +{ + if (!(self.options & YYWebImageOptionUseNSURLCache)) { + // Prevents caching of responses + cachedResponse = nil; + } + if (completionHandler) { + completionHandler(cachedResponse); + } +} + +#pragma mark NSURLSessionTaskDelegate + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error +{ + if (nil==error) { + @autoreleasepool { + [_lock lock]; + _dataTask = 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; } - } 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 + + /* + 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; } - } break; - default: { - self.data = nil; // clear the data, re-encode for disk cache - } break; + 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]; } - if ([self isCancelled]) return; + } + [_lock unlock]; + } + + } else { + @autoreleasepool { + [_lock lock]; + if (![self isCancelled]) { + if (_completion) { + _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error); + } + _dataTask = nil; + _data = nil; + if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) { + [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount]; + } + [self _finish]; - if (self.transform && image) { - UIImage *newImage = self.transform(image, self.request.URL); - if (newImage != image) { - self.data = nil; + if (_options & YYWebImageOptionIgnoreFailedURL) { + if (error.code != NSURLErrorNotConnectedToInternet && + error.code != NSURLErrorCancelled && + error.code != NSURLErrorTimedOut && + error.code != NSURLErrorUserCancelledAuthentication && + error.code != NSURLErrorNetworkConnectionLost) { + URLInBlackListAdd(_request.URL); } - 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]; } - [_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); - } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task +didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler +{ + NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; + __block NSURLCredential *credential = nil; + + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + if (!(self.options & YYWebImageOptionAllowInvalidSSLCertificates)) { + disposition = NSURLSessionAuthChallengePerformDefaultHandling; + } else { + credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; + disposition = NSURLSessionAuthChallengeUseCredential; + } + } else { + if (challenge.previousFailureCount == 0) { + if (self.credential) { + credential = self.credential; + disposition = NSURLSessionAuthChallengeUseCredential; + } else { + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } + } else { + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } - [_lock unlock]; + } + + if (completionHandler) { + completionHandler(disposition, credential); } }