YYWebImageOperation.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. //
  2. // YYWebImageOperation.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/2/15.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYWebImageOperation.h"
  12. #import "UIApplication+YYAdd.h"
  13. #import "YYImage.h"
  14. #import "YYWeakProxy.h"
  15. #import "UIImage+YYAdd.h"
  16. #import <ImageIO/ImageIO.h>
  17. #import "YYKitMacro.h"
  18. #if __has_include("YYDispatchQueuePool.h")
  19. #import "YYDispatchQueuePool.h"
  20. #else
  21. #import <libkern/OSAtomic.h>
  22. #endif
  23. #define MIN_PROGRESSIVE_TIME_INTERVAL 0.2
  24. #define MIN_PROGRESSIVE_BLUR_TIME_INTERVAL 0.4
  25. /// Returns YES if the right-bottom pixel is filled.
  26. static BOOL YYCGImageLastPixelFilled(CGImageRef image) {
  27. if (!image) return NO;
  28. size_t width = CGImageGetWidth(image);
  29. size_t height = CGImageGetHeight(image);
  30. if (width == 0 || height == 0) return NO;
  31. CGContextRef ctx = CGBitmapContextCreate(NULL, 1, 1, 8, 0, YYCGColorSpaceGetDeviceRGB(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault);
  32. if (!ctx) return NO;
  33. CGContextDrawImage(ctx, CGRectMake( -(int)width + 1, 0, width, height), image);
  34. uint8_t *bytes = CGBitmapContextGetData(ctx);
  35. BOOL isAlpha = bytes && bytes[0] == 0;
  36. CFRelease(ctx);
  37. return !isAlpha;
  38. }
  39. /// Returns JPEG SOS (Start Of Scan) Marker
  40. static NSData *JPEGSOSMarker() {
  41. // "Start Of Scan" Marker
  42. static NSData *marker = nil;
  43. static dispatch_once_t onceToken;
  44. dispatch_once(&onceToken, ^{
  45. uint8_t bytes[2] = {0xFF, 0xDA};
  46. marker = [NSData dataWithBytes:bytes length:2];
  47. });
  48. return marker;
  49. }
  50. static NSMutableSet *URLBlacklist;
  51. static dispatch_semaphore_t URLBlacklistLock;
  52. static void URLBlacklistInit() {
  53. static dispatch_once_t onceToken;
  54. dispatch_once(&onceToken, ^{
  55. URLBlacklist = [NSMutableSet new];
  56. URLBlacklistLock = dispatch_semaphore_create(1);
  57. });
  58. }
  59. static BOOL URLBlackListContains(NSURL *url) {
  60. if (!url || url == (id)[NSNull null]) return NO;
  61. URLBlacklistInit();
  62. dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
  63. BOOL contains = [URLBlacklist containsObject:url];
  64. dispatch_semaphore_signal(URLBlacklistLock);
  65. return contains;
  66. }
  67. static void URLInBlackListAdd(NSURL *url) {
  68. if (!url || url == (id)[NSNull null]) return;
  69. URLBlacklistInit();
  70. dispatch_semaphore_wait(URLBlacklistLock, DISPATCH_TIME_FOREVER);
  71. [URLBlacklist addObject:url];
  72. dispatch_semaphore_signal(URLBlacklistLock);
  73. }
  74. @interface YYWebImageOperation() <NSURLConnectionDelegate>
  75. @property (readwrite, getter=isExecuting) BOOL executing;
  76. @property (readwrite, getter=isFinished) BOOL finished;
  77. @property (readwrite, getter=isCancelled) BOOL cancelled;
  78. @property (readwrite, getter=isStarted) BOOL started;
  79. @property (nonatomic, strong) NSRecursiveLock *lock;
  80. @property (nonatomic, strong) NSURLConnection *connection;
  81. @property (nonatomic, strong) NSMutableData *data;
  82. @property (nonatomic, assign) NSInteger expectedSize;
  83. @property (nonatomic, assign) UIBackgroundTaskIdentifier taskID;
  84. @property (nonatomic, assign) NSTimeInterval lastProgressiveDecodeTimestamp;
  85. @property (nonatomic, strong) YYImageDecoder *progressiveDecoder;
  86. @property (nonatomic, assign) BOOL progressiveIgnored;
  87. @property (nonatomic, assign) BOOL progressiveDetected;
  88. @property (nonatomic, assign) NSUInteger progressiveScanedLength;
  89. @property (nonatomic, assign) NSUInteger progressiveDisplayCount;
  90. @property (nonatomic, copy) YYWebImageProgressBlock progress;
  91. @property (nonatomic, copy) YYWebImageTransformBlock transform;
  92. @property (nonatomic, copy) YYWebImageCompletionBlock completion;
  93. @end
  94. @implementation YYWebImageOperation
  95. @synthesize executing = _executing;
  96. @synthesize finished = _finished;
  97. @synthesize cancelled = _cancelled;
  98. /// Network thread entry point.
  99. + (void)_networkThreadMain:(id)object {
  100. @autoreleasepool {
  101. [[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"];
  102. NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
  103. [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
  104. [runLoop run];
  105. }
  106. }
  107. /// Global image request network thread, used by NSURLConnection delegate.
  108. + (NSThread *)_networkThread {
  109. static NSThread *thread = nil;
  110. static dispatch_once_t onceToken;
  111. dispatch_once(&onceToken, ^{
  112. thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
  113. if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
  114. thread.qualityOfService = NSQualityOfServiceBackground;
  115. }
  116. [thread start];
  117. });
  118. return thread;
  119. }
  120. /// Global image queue, used for image reading and decoding.
  121. + (dispatch_queue_t)_imageQueue {
  122. #ifdef YYDispatchQueuePool_h
  123. return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
  124. #else
  125. #define MAX_QUEUE_COUNT 16
  126. static int queueCount;
  127. static dispatch_queue_t queues[MAX_QUEUE_COUNT];
  128. static dispatch_once_t onceToken;
  129. static int32_t counter = 0;
  130. dispatch_once(&onceToken, ^{
  131. queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
  132. queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
  133. if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
  134. for (NSUInteger i = 0; i < queueCount; i++) {
  135. dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
  136. queues[i] = dispatch_queue_create("com.ibireme.yykit.decode", attr);
  137. }
  138. } else {
  139. for (NSUInteger i = 0; i < queueCount; i++) {
  140. queues[i] = dispatch_queue_create("com.ibireme.yykit.decode", DISPATCH_QUEUE_SERIAL);
  141. dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
  142. }
  143. }
  144. });
  145. int32_t cur = OSAtomicIncrement32(&counter);
  146. if (cur < 0) cur = -cur;
  147. return queues[(cur) % queueCount];
  148. #undef MAX_QUEUE_COUNT
  149. #endif
  150. }
  151. - (instancetype)init {
  152. @throw [NSException exceptionWithName:@"YYWebImageOperation init error" reason:@"YYWebImageOperation must be initialized with a request. Use the designated initializer to init." userInfo:nil];
  153. return [self initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@""]] options:0 cache:nil cacheKey:nil progress:nil transform:nil completion:nil];
  154. }
  155. - (instancetype)initWithRequest:(NSURLRequest *)request
  156. options:(YYWebImageOptions)options
  157. cache:(YYImageCache *)cache
  158. cacheKey:(NSString *)cacheKey
  159. progress:(YYWebImageProgressBlock)progress
  160. transform:(YYWebImageTransformBlock)transform
  161. completion:(YYWebImageCompletionBlock)completion {
  162. self = [super init];
  163. if (!self) return nil;
  164. if (!request) return nil;
  165. _request = request;
  166. _options = options;
  167. _cache = cache;
  168. _cacheKey = cacheKey ? cacheKey : request.URL.absoluteString;
  169. _shouldUseCredentialStorage = YES;
  170. _progress = progress;
  171. _transform = transform;
  172. _completion = completion;
  173. _executing = NO;
  174. _finished = NO;
  175. _cancelled = NO;
  176. _taskID = UIBackgroundTaskInvalid;
  177. _lock = [NSRecursiveLock new];
  178. return self;
  179. }
  180. - (void)dealloc {
  181. [_lock lock];
  182. if (_taskID != UIBackgroundTaskInvalid) {
  183. [[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
  184. _taskID = UIBackgroundTaskInvalid;
  185. }
  186. if ([self isExecuting]) {
  187. self.cancelled = YES;
  188. self.finished = YES;
  189. if (_connection) {
  190. [_connection cancel];
  191. if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
  192. [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
  193. }
  194. }
  195. if (_completion) {
  196. @autoreleasepool {
  197. _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
  198. }
  199. }
  200. }
  201. [_lock unlock];
  202. }
  203. - (void)_endBackgroundTask {
  204. [_lock lock];
  205. if (_taskID != UIBackgroundTaskInvalid) {
  206. [[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
  207. _taskID = UIBackgroundTaskInvalid;
  208. }
  209. [_lock unlock];
  210. }
  211. #pragma mark - Runs in operation thread
  212. - (void)_finish {
  213. self.executing = NO;
  214. self.finished = YES;
  215. [self _endBackgroundTask];
  216. }
  217. // runs on network thread
  218. - (void)_startOperation {
  219. if ([self isCancelled]) return;
  220. @autoreleasepool {
  221. // get image from cache
  222. if (_cache &&
  223. !(_options & YYWebImageOptionUseNSURLCache) &&
  224. !(_options & YYWebImageOptionRefreshImageCache)) {
  225. UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
  226. if (image) {
  227. [_lock lock];
  228. if (![self isCancelled]) {
  229. if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
  230. }
  231. [self _finish];
  232. [_lock unlock];
  233. return;
  234. }
  235. if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
  236. __weak typeof(self) _self = self;
  237. dispatch_async([self.class _imageQueue], ^{
  238. __strong typeof(_self) self = _self;
  239. if (!self || [self isCancelled]) return;
  240. UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
  241. if (image) {
  242. [self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
  243. [self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
  244. } else {
  245. [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
  246. }
  247. });
  248. return;
  249. }
  250. }
  251. }
  252. [self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
  253. }
  254. // runs on network thread
  255. - (void)_startRequest:(id)object {
  256. if ([self isCancelled]) return;
  257. @autoreleasepool {
  258. if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
  259. NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
  260. [_lock lock];
  261. if (![self isCancelled]) {
  262. if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
  263. }
  264. [self _finish];
  265. [_lock unlock];
  266. return;
  267. }
  268. if (_request.URL.isFileURL) {
  269. NSArray *keys = @[NSURLFileSizeKey];
  270. NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil];
  271. NSNumber *fileSize = attr[NSURLFileSizeKey];
  272. _expectedSize = (fileSize != nil) ? fileSize.unsignedIntegerValue : -1;
  273. }
  274. // request image from web
  275. [_lock lock];
  276. if (![self isCancelled]) {
  277. _connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[YYWeakProxy proxyWithTarget:self]];
  278. if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
  279. [[UIApplication sharedExtensionApplication] incrementNetworkActivityCount];
  280. }
  281. }
  282. [_lock unlock];
  283. }
  284. }
  285. // runs on network thread, called from outer "cancel"
  286. - (void)_cancelOperation {
  287. @autoreleasepool {
  288. if (_connection) {
  289. if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
  290. [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
  291. }
  292. }
  293. [_connection cancel];
  294. _connection = nil;
  295. if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
  296. [self _endBackgroundTask];
  297. }
  298. }
  299. // runs on network thread
  300. - (void)_didReceiveImageFromDiskCache:(UIImage *)image {
  301. @autoreleasepool {
  302. [_lock lock];
  303. if (![self isCancelled]) {
  304. if (image) {
  305. if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil);
  306. [self _finish];
  307. } else {
  308. [self _startRequest:nil];
  309. }
  310. }
  311. [_lock unlock];
  312. }
  313. }
  314. - (void)_didReceiveImageFromWeb:(UIImage *)image {
  315. @autoreleasepool {
  316. [_lock lock];
  317. if (![self isCancelled]) {
  318. if (_cache) {
  319. if (image || (_options & YYWebImageOptionRefreshImageCache)) {
  320. NSData *data = _data;
  321. dispatch_async([YYWebImageOperation _imageQueue], ^{
  322. YYImageCacheType cacheType = (_options & YYWebImageOptionIgnoreDiskCache) ? YYImageCacheTypeMemory : YYImageCacheTypeAll;
  323. [_cache setImage:image imageData:data forKey:_cacheKey withType:cacheType];
  324. });
  325. }
  326. }
  327. _data = nil;
  328. NSError *error = nil;
  329. if (!image) {
  330. error = [NSError errorWithDomain:@"com.ibireme.yykit.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
  331. if (_options & YYWebImageOptionIgnoreFailedURL) {
  332. if (URLBlackListContains(_request.URL)) {
  333. error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
  334. } else {
  335. URLInBlackListAdd(_request.URL);
  336. }
  337. }
  338. }
  339. if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
  340. [self _finish];
  341. }
  342. [_lock unlock];
  343. }
  344. }
  345. #pragma mark - NSURLConnectionDelegate runs in operation thread
  346. - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection {
  347. return _shouldUseCredentialStorage;
  348. }
  349. - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  350. @autoreleasepool {
  351. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  352. if (!(_options & YYWebImageOptionAllowInvalidSSLCertificates) &&
  353. [challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {
  354. [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
  355. } else {
  356. NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  357. [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
  358. }
  359. } else {
  360. if ([challenge previousFailureCount] == 0) {
  361. if (_credential) {
  362. [[challenge sender] useCredential:_credential forAuthenticationChallenge:challenge];
  363. } else {
  364. [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
  365. }
  366. } else {
  367. [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
  368. }
  369. }
  370. }
  371. }
  372. - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
  373. if (!cachedResponse) return cachedResponse;
  374. if (_options & YYWebImageOptionUseNSURLCache) {
  375. return cachedResponse;
  376. } else {
  377. // ignore NSURLCache
  378. return nil;
  379. }
  380. }
  381. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  382. @autoreleasepool {
  383. NSError *error = nil;
  384. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  385. NSHTTPURLResponse *httpResponse = (id) response;
  386. NSInteger statusCode = httpResponse.statusCode;
  387. if (statusCode >= 400 || statusCode == 304) {
  388. error = [NSError errorWithDomain:NSURLErrorDomain code:statusCode userInfo:nil];
  389. }
  390. }
  391. if (error) {
  392. [_connection cancel];
  393. [self connection:_connection didFailWithError:error];
  394. } else {
  395. if (response.expectedContentLength) {
  396. _expectedSize = (NSInteger)response.expectedContentLength;
  397. if (_expectedSize < 0) _expectedSize = -1;
  398. }
  399. _data = [NSMutableData dataWithCapacity:_expectedSize > 0 ? _expectedSize : 0];
  400. if (_progress) {
  401. [_lock lock];
  402. if (![self isCancelled]) _progress(0, _expectedSize);
  403. [_lock unlock];
  404. }
  405. }
  406. }
  407. }
  408. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  409. @autoreleasepool {
  410. [_lock lock];
  411. BOOL canceled = [self isCancelled];
  412. [_lock unlock];
  413. if (canceled) return;
  414. if (data) [_data appendData:data];
  415. if (_progress) {
  416. [_lock lock];
  417. if (![self isCancelled]) {
  418. _progress(_data.length, _expectedSize);
  419. }
  420. [_lock unlock];
  421. }
  422. /*--------------------------- progressive ----------------------------*/
  423. BOOL progressive = (_options & YYWebImageOptionProgressive) > 0;
  424. BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0;
  425. if (!_completion || !(progressive || progressiveBlur)) return;
  426. if (data.length <= 16) return;
  427. if (_expectedSize > 0 && data.length >= _expectedSize * 0.99) return;
  428. if (_progressiveIgnored) return;
  429. NSTimeInterval min = progressiveBlur ? MIN_PROGRESSIVE_BLUR_TIME_INTERVAL : MIN_PROGRESSIVE_TIME_INTERVAL;
  430. NSTimeInterval now = CACurrentMediaTime();
  431. if (now - _lastProgressiveDecodeTimestamp < min) return;
  432. if (!_progressiveDecoder) {
  433. _progressiveDecoder = [[YYImageDecoder alloc] initWithScale:[UIScreen mainScreen].scale];
  434. }
  435. [_progressiveDecoder updateData:_data final:NO];
  436. if ([self isCancelled]) return;
  437. if (_progressiveDecoder.type == YYImageTypeUnknown ||
  438. _progressiveDecoder.type == YYImageTypeWebP ||
  439. _progressiveDecoder.type == YYImageTypeOther) {
  440. _progressiveDecoder = nil;
  441. _progressiveIgnored = YES;
  442. return;
  443. }
  444. if (progressiveBlur) { // only support progressive JPEG and interlaced PNG
  445. if (_progressiveDecoder.type != YYImageTypeJPEG &&
  446. _progressiveDecoder.type != YYImageTypePNG) {
  447. _progressiveDecoder = nil;
  448. _progressiveIgnored = YES;
  449. return;
  450. }
  451. }
  452. if (_progressiveDecoder.frameCount == 0) return;
  453. if (!progressiveBlur) {
  454. YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
  455. if (frame.image) {
  456. [_lock lock];
  457. if (![self isCancelled]) {
  458. _completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
  459. _lastProgressiveDecodeTimestamp = now;
  460. }
  461. [_lock unlock];
  462. }
  463. return;
  464. } else {
  465. if (_progressiveDecoder.type == YYImageTypeJPEG) {
  466. if (!_progressiveDetected) {
  467. NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
  468. NSDictionary *jpeg = dic[(id)kCGImagePropertyJFIFDictionary];
  469. NSNumber *isProg = jpeg[(id)kCGImagePropertyJFIFIsProgressive];
  470. if (!isProg.boolValue) {
  471. _progressiveIgnored = YES;
  472. _progressiveDecoder = nil;
  473. return;
  474. }
  475. _progressiveDetected = YES;
  476. }
  477. NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4;
  478. if (scanLength <= 2) return;
  479. NSRange scanRange = NSMakeRange(_progressiveScanedLength, scanLength);
  480. NSRange markerRange = [_data rangeOfData:JPEGSOSMarker() options:kNilOptions range:scanRange];
  481. _progressiveScanedLength = _data.length;
  482. if (markerRange.location == NSNotFound) return;
  483. if ([self isCancelled]) return;
  484. } else if (_progressiveDecoder.type == YYImageTypePNG) {
  485. if (!_progressiveDetected) {
  486. NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
  487. NSDictionary *png = dic[(id)kCGImagePropertyPNGDictionary];
  488. NSNumber *isProg = png[(id)kCGImagePropertyPNGInterlaceType];
  489. if (!isProg.boolValue) {
  490. _progressiveIgnored = YES;
  491. _progressiveDecoder = nil;
  492. return;
  493. }
  494. _progressiveDetected = YES;
  495. }
  496. }
  497. YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
  498. UIImage *image = frame.image;
  499. if (!image) return;
  500. if ([self isCancelled]) return;
  501. if (!YYCGImageLastPixelFilled(image.CGImage)) return;
  502. _progressiveDisplayCount++;
  503. CGFloat radius = 32;
  504. if (_expectedSize > 0) {
  505. radius *= 1.0 / (3 * _data.length / (CGFloat)_expectedSize + 0.6) - 0.25;
  506. } else {
  507. radius /= (_progressiveDisplayCount);
  508. }
  509. image = [image imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil];
  510. if (image) {
  511. [_lock lock];
  512. if (![self isCancelled]) {
  513. _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
  514. _lastProgressiveDecodeTimestamp = now;
  515. }
  516. [_lock unlock];
  517. }
  518. }
  519. }
  520. }
  521. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  522. @autoreleasepool {
  523. [_lock lock];
  524. _connection = nil;
  525. if (![self isCancelled]) {
  526. __weak typeof(self) _self = self;
  527. dispatch_async([self.class _imageQueue], ^{
  528. __strong typeof(_self) self = _self;
  529. if (!self) return;
  530. BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0;
  531. BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0;
  532. UIImage *image;
  533. BOOL hasAnimation = NO;
  534. if (allowAnimation) {
  535. image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale];
  536. if (shouldDecode) image = [image imageByDecoded];
  537. if ([((YYImage *)image) animatedImageFrameCount] > 1) {
  538. hasAnimation = YES;
  539. }
  540. } else {
  541. YYImageDecoder *decoder = [YYImageDecoder decoderWithData:self.data scale:[UIScreen mainScreen].scale];
  542. image = [decoder frameAtIndex:0 decodeForDisplay:shouldDecode].image;
  543. }
  544. /*
  545. If the image has animation, save the original image data to disk cache.
  546. If the image is not PNG or JPEG, re-encode the image to PNG or JPEG for
  547. better decoding performance.
  548. */
  549. YYImageType imageType = YYImageDetectType((__bridge CFDataRef)self.data);
  550. switch (imageType) {
  551. case YYImageTypeJPEG:
  552. case YYImageTypeGIF:
  553. case YYImageTypePNG:
  554. case YYImageTypeWebP: { // save to disk cache
  555. if (!hasAnimation) {
  556. if (imageType == YYImageTypeGIF ||
  557. imageType == YYImageTypeWebP) {
  558. self.data = nil; // clear the data, re-encode for disk cache
  559. }
  560. }
  561. } break;
  562. default: {
  563. self.data = nil; // clear the data, re-encode for disk cache
  564. } break;
  565. }
  566. if ([self isCancelled]) return;
  567. if (self.transform && image) {
  568. UIImage *newImage = self.transform(image, self.request.URL);
  569. if (newImage != image) {
  570. self.data = nil;
  571. }
  572. image = newImage;
  573. if ([self isCancelled]) return;
  574. }
  575. [self performSelector:@selector(_didReceiveImageFromWeb:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
  576. });
  577. if (![self.request.URL isFileURL] && (self.options & YYWebImageOptionShowNetworkActivity)) {
  578. [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
  579. }
  580. }
  581. [_lock unlock];
  582. }
  583. }
  584. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  585. @autoreleasepool {
  586. [_lock lock];
  587. if (![self isCancelled]) {
  588. if (_completion) {
  589. _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
  590. }
  591. _connection = nil;
  592. _data = nil;
  593. if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
  594. [[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
  595. }
  596. [self _finish];
  597. if (_options & YYWebImageOptionIgnoreFailedURL) {
  598. if (error.code != NSURLErrorNotConnectedToInternet &&
  599. error.code != NSURLErrorCancelled &&
  600. error.code != NSURLErrorTimedOut &&
  601. error.code != NSURLErrorUserCancelledAuthentication &&
  602. error.code != NSURLErrorNetworkConnectionLost) {
  603. URLInBlackListAdd(_request.URL);
  604. }
  605. }
  606. }
  607. [_lock unlock];
  608. }
  609. }
  610. #pragma mark - Override NSOperation
  611. - (void)start {
  612. @autoreleasepool {
  613. [_lock lock];
  614. self.started = YES;
  615. if ([self isCancelled]) {
  616. [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
  617. self.finished = YES;
  618. } else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
  619. if (!_request) {
  620. self.finished = YES;
  621. if (_completion) {
  622. NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
  623. _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
  624. }
  625. } else {
  626. self.executing = YES;
  627. [self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
  628. if ((_options & YYWebImageOptionAllowBackgroundTask) && ![UIApplication isAppExtension]) {
  629. __weak __typeof__ (self) _self = self;
  630. if (_taskID == UIBackgroundTaskInvalid) {
  631. _taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{
  632. __strong __typeof (_self) self = _self;
  633. if (self) {
  634. [self cancel];
  635. self.finished = YES;
  636. }
  637. }];
  638. }
  639. }
  640. }
  641. }
  642. [_lock unlock];
  643. }
  644. }
  645. - (void)cancel {
  646. [_lock lock];
  647. if (![self isCancelled]) {
  648. [super cancel];
  649. self.cancelled = YES;
  650. if ([self isExecuting]) {
  651. self.executing = NO;
  652. [self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
  653. }
  654. if (self.started) {
  655. self.finished = YES;
  656. }
  657. }
  658. [_lock unlock];
  659. }
  660. - (void)setExecuting:(BOOL)executing {
  661. [_lock lock];
  662. if (_executing != executing) {
  663. [self willChangeValueForKey:@"isExecuting"];
  664. _executing = executing;
  665. [self didChangeValueForKey:@"isExecuting"];
  666. }
  667. [_lock unlock];
  668. }
  669. - (BOOL)isExecuting {
  670. [_lock lock];
  671. BOOL executing = _executing;
  672. [_lock unlock];
  673. return executing;
  674. }
  675. - (void)setFinished:(BOOL)finished {
  676. [_lock lock];
  677. if (_finished != finished) {
  678. [self willChangeValueForKey:@"isFinished"];
  679. _finished = finished;
  680. [self didChangeValueForKey:@"isFinished"];
  681. }
  682. [_lock unlock];
  683. }
  684. - (BOOL)isFinished {
  685. [_lock lock];
  686. BOOL finished = _finished;
  687. [_lock unlock];
  688. return finished;
  689. }
  690. - (void)setCancelled:(BOOL)cancelled {
  691. [_lock lock];
  692. if (_cancelled != cancelled) {
  693. [self willChangeValueForKey:@"isCancelled"];
  694. _cancelled = cancelled;
  695. [self didChangeValueForKey:@"isCancelled"];
  696. }
  697. [_lock unlock];
  698. }
  699. - (BOOL)isCancelled {
  700. [_lock lock];
  701. BOOL cancelled = _cancelled;
  702. [_lock unlock];
  703. return cancelled;
  704. }
  705. - (BOOL)isConcurrent {
  706. return YES;
  707. }
  708. - (BOOL)isAsynchronous {
  709. return YES;
  710. }
  711. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  712. if ([key isEqualToString:@"isExecuting"] ||
  713. [key isEqualToString:@"isFinished"] ||
  714. [key isEqualToString:@"isCancelled"]) {
  715. return NO;
  716. }
  717. return [super automaticallyNotifiesObserversForKey:key];
  718. }
  719. - (NSString *)description {
  720. NSMutableString *string = [NSMutableString stringWithFormat:@"<%@: %p ",self.class, self];
  721. [string appendFormat:@" executing:%@", [self isExecuting] ? @"YES" : @"NO"];
  722. [string appendFormat:@" finished:%@", [self isFinished] ? @"YES" : @"NO"];
  723. [string appendFormat:@" cancelled:%@", [self isCancelled] ? @"YES" : @"NO"];
  724. [string appendString:@">"];
  725. return string;
  726. }
  727. @end