SDWebImageDownloaderOperation.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. /*
  2. * This file is part of the SDWebImage package.
  3. * (c) Olivier Poitrey <rs@dailymotion.com>
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. */
  8. #import "SDWebImageDownloaderOperation.h"
  9. #import "SDWebImageDecoder.h"
  10. #import "UIImage+MultiFormat.h"
  11. #import <ImageIO/ImageIO.h>
  12. #import "SDWebImageManager.h"
  13. #import "NSImage+WebCache.h"
  14. NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
  15. NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
  16. NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
  17. NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
  18. static NSString *const kProgressCallbackKey = @"progress";
  19. static NSString *const kCompletedCallbackKey = @"completed";
  20. typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
  21. @interface SDWebImageDownloaderOperation ()
  22. @property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
  23. @property (assign, nonatomic, getter = isExecuting) BOOL executing;
  24. @property (assign, nonatomic, getter = isFinished) BOOL finished;
  25. @property (strong, nonatomic, nullable) NSMutableData *imageData;
  26. @property (copy, nonatomic, nullable) NSData *cachedData;
  27. // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run
  28. // the task associated with this operation
  29. @property (weak, nonatomic, nullable) NSURLSession *unownedSession;
  30. // This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
  31. @property (strong, nonatomic, nullable) NSURLSession *ownedSession;
  32. @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
  33. @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
  34. #if SD_UIKIT
  35. @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
  36. #endif
  37. @end
  38. @implementation SDWebImageDownloaderOperation {
  39. size_t _width, _height;
  40. #if SD_UIKIT || SD_WATCH
  41. UIImageOrientation _orientation;
  42. #endif
  43. CGImageSourceRef _imageSource;
  44. }
  45. @synthesize executing = _executing;
  46. @synthesize finished = _finished;
  47. - (nonnull instancetype)init {
  48. return [self initWithRequest:nil inSession:nil options:0];
  49. }
  50. - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
  51. inSession:(nullable NSURLSession *)session
  52. options:(SDWebImageDownloaderOptions)options {
  53. if ((self = [super init])) {
  54. _request = [request copy];
  55. _shouldDecompressImages = YES;
  56. _options = options;
  57. _callbackBlocks = [NSMutableArray new];
  58. _executing = NO;
  59. _finished = NO;
  60. _expectedSize = 0;
  61. _unownedSession = session;
  62. _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
  63. }
  64. return self;
  65. }
  66. - (void)dealloc {
  67. SDDispatchQueueRelease(_barrierQueue);
  68. if (_imageSource) {
  69. CFRelease(_imageSource);
  70. _imageSource = NULL;
  71. }
  72. }
  73. - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  74. completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
  75. SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
  76. if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
  77. if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
  78. dispatch_barrier_async(self.barrierQueue, ^{
  79. [self.callbackBlocks addObject:callbacks];
  80. });
  81. return callbacks;
  82. }
  83. - (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
  84. __block NSMutableArray<id> *callbacks = nil;
  85. dispatch_sync(self.barrierQueue, ^{
  86. // We need to remove [NSNull null] because there might not always be a progress block for each callback
  87. callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
  88. [callbacks removeObjectIdenticalTo:[NSNull null]];
  89. });
  90. return [callbacks copy]; // strip mutability here
  91. }
  92. - (BOOL)cancel:(nullable id)token {
  93. __block BOOL shouldCancel = NO;
  94. dispatch_barrier_sync(self.barrierQueue, ^{
  95. [self.callbackBlocks removeObjectIdenticalTo:token];
  96. if (self.callbackBlocks.count == 0) {
  97. shouldCancel = YES;
  98. }
  99. });
  100. if (shouldCancel) {
  101. [self cancel];
  102. }
  103. return shouldCancel;
  104. }
  105. - (void)start {
  106. @synchronized (self) {
  107. if (self.isCancelled) {
  108. self.finished = YES;
  109. [self reset];
  110. return;
  111. }
  112. #if SD_UIKIT
  113. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  114. BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
  115. if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
  116. __weak __typeof__ (self) wself = self;
  117. UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
  118. self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
  119. __strong __typeof (wself) sself = wself;
  120. if (sself) {
  121. [sself cancel];
  122. [app endBackgroundTask:sself.backgroundTaskId];
  123. sself.backgroundTaskId = UIBackgroundTaskInvalid;
  124. }
  125. }];
  126. }
  127. #endif
  128. if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
  129. // Grab the cached data for later check
  130. NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
  131. if (cachedResponse) {
  132. self.cachedData = cachedResponse.data;
  133. }
  134. }
  135. NSURLSession *session = self.unownedSession;
  136. if (!self.unownedSession) {
  137. NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  138. sessionConfig.timeoutIntervalForRequest = 15;
  139. /**
  140. * Create the session for this task
  141. * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  142. * method calls and completion handler calls.
  143. */
  144. self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
  145. delegate:self
  146. delegateQueue:nil];
  147. session = self.ownedSession;
  148. }
  149. self.dataTask = [session dataTaskWithRequest:self.request];
  150. self.executing = YES;
  151. }
  152. [self.dataTask resume];
  153. if (self.dataTask) {
  154. for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
  155. progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
  156. }
  157. __weak typeof(self) weakSelf = self;
  158. dispatch_async(dispatch_get_main_queue(), ^{
  159. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
  160. });
  161. } else {
  162. [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
  163. }
  164. #if SD_UIKIT
  165. Class UIApplicationClass = NSClassFromString(@"UIApplication");
  166. if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
  167. return;
  168. }
  169. if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
  170. UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
  171. [app endBackgroundTask:self.backgroundTaskId];
  172. self.backgroundTaskId = UIBackgroundTaskInvalid;
  173. }
  174. #endif
  175. }
  176. - (void)cancel {
  177. @synchronized (self) {
  178. [self cancelInternal];
  179. }
  180. }
  181. - (void)cancelInternal {
  182. if (self.isFinished) return;
  183. [super cancel];
  184. if (self.dataTask) {
  185. [self.dataTask cancel];
  186. __weak typeof(self) weakSelf = self;
  187. dispatch_async(dispatch_get_main_queue(), ^{
  188. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
  189. });
  190. // As we cancelled the connection, its callback won't be called and thus won't
  191. // maintain the isFinished and isExecuting flags.
  192. if (self.isExecuting) self.executing = NO;
  193. if (!self.isFinished) self.finished = YES;
  194. }
  195. [self reset];
  196. }
  197. - (void)done {
  198. self.finished = YES;
  199. self.executing = NO;
  200. [self reset];
  201. }
  202. - (void)reset {
  203. __weak typeof(self) weakSelf = self;
  204. dispatch_barrier_async(self.barrierQueue, ^{
  205. [weakSelf.callbackBlocks removeAllObjects];
  206. });
  207. self.dataTask = nil;
  208. NSOperationQueue *delegateQueue;
  209. if (self.unownedSession) {
  210. delegateQueue = self.unownedSession.delegateQueue;
  211. } else {
  212. delegateQueue = self.ownedSession.delegateQueue;
  213. }
  214. if (delegateQueue) {
  215. NSAssert(delegateQueue.maxConcurrentOperationCount == 1, @"NSURLSession delegate queue should be a serial queue");
  216. [delegateQueue addOperationWithBlock:^{
  217. weakSelf.imageData = nil;
  218. }];
  219. }
  220. if (self.ownedSession) {
  221. [self.ownedSession invalidateAndCancel];
  222. self.ownedSession = nil;
  223. }
  224. }
  225. - (void)setFinished:(BOOL)finished {
  226. [self willChangeValueForKey:@"isFinished"];
  227. _finished = finished;
  228. [self didChangeValueForKey:@"isFinished"];
  229. }
  230. - (void)setExecuting:(BOOL)executing {
  231. [self willChangeValueForKey:@"isExecuting"];
  232. _executing = executing;
  233. [self didChangeValueForKey:@"isExecuting"];
  234. }
  235. - (BOOL)isConcurrent {
  236. return YES;
  237. }
  238. #pragma mark NSURLSessionDataDelegate
  239. - (void)URLSession:(NSURLSession *)session
  240. dataTask:(NSURLSessionDataTask *)dataTask
  241. didReceiveResponse:(NSURLResponse *)response
  242. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  243. //'304 Not Modified' is an exceptional one
  244. if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
  245. NSInteger expected = (NSInteger)response.expectedContentLength;
  246. expected = expected > 0 ? expected : 0;
  247. self.expectedSize = expected;
  248. for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
  249. progressBlock(0, expected, self.request.URL);
  250. }
  251. self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
  252. self.response = response;
  253. __weak typeof(self) weakSelf = self;
  254. dispatch_async(dispatch_get_main_queue(), ^{
  255. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
  256. });
  257. } else {
  258. NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
  259. //This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
  260. //In case of 304 we need just cancel the operation and return cached image from the cache.
  261. if (code == 304) {
  262. [self cancelInternal];
  263. } else {
  264. [self.dataTask cancel];
  265. }
  266. __weak typeof(self) weakSelf = self;
  267. dispatch_async(dispatch_get_main_queue(), ^{
  268. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
  269. });
  270. [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
  271. [self done];
  272. }
  273. if (completionHandler) {
  274. completionHandler(NSURLSessionResponseAllow);
  275. }
  276. }
  277. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  278. [self.imageData appendData:data];
  279. if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
  280. // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
  281. // Thanks to the author @Nyx0uf
  282. // Get the image data
  283. NSData *imageData = [self.imageData copy];
  284. // Get the total bytes downloaded
  285. const NSInteger totalSize = imageData.length;
  286. // Get the finish status
  287. BOOL finished = (totalSize >= self.expectedSize);
  288. if (!_imageSource) {
  289. _imageSource = CGImageSourceCreateIncremental(NULL);
  290. }
  291. // Update the data source, we must pass ALL the data, not just the new bytes
  292. CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)imageData, finished);
  293. if (_width + _height == 0) {
  294. CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
  295. if (properties) {
  296. NSInteger orientationValue = -1;
  297. CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
  298. if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
  299. val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
  300. if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
  301. val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
  302. if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
  303. CFRelease(properties);
  304. // When we draw to Core Graphics, we lose orientation information,
  305. // which means the image below born of initWithCGIImage will be
  306. // oriented incorrectly sometimes. (Unlike the image born of initWithData
  307. // in didCompleteWithError.) So save it here and pass it on later.
  308. #if SD_UIKIT || SD_WATCH
  309. _orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
  310. #endif
  311. }
  312. }
  313. if (_width + _height > 0 && !finished) {
  314. // Create the image
  315. CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
  316. #if SD_UIKIT || SD_WATCH
  317. // Workaround for iOS anamorphic image
  318. if (partialImageRef) {
  319. const size_t partialHeight = CGImageGetHeight(partialImageRef);
  320. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  321. CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, _width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
  322. CGColorSpaceRelease(colorSpace);
  323. if (bmContext) {
  324. CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
  325. CGImageRelease(partialImageRef);
  326. partialImageRef = CGBitmapContextCreateImage(bmContext);
  327. CGContextRelease(bmContext);
  328. }
  329. else {
  330. CGImageRelease(partialImageRef);
  331. partialImageRef = nil;
  332. }
  333. }
  334. #endif
  335. if (partialImageRef) {
  336. #if SD_UIKIT || SD_WATCH
  337. UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:_orientation];
  338. #elif SD_MAC
  339. UIImage *image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
  340. #endif
  341. CGImageRelease(partialImageRef);
  342. NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
  343. UIImage *scaledImage = [self scaledImageForKey:key image:image];
  344. if (self.shouldDecompressImages) {
  345. image = [UIImage decodedImageWithImage:scaledImage];
  346. }
  347. else {
  348. image = scaledImage;
  349. }
  350. [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
  351. }
  352. }
  353. if (finished) {
  354. if (_imageSource) {
  355. CFRelease(_imageSource);
  356. _imageSource = NULL;
  357. }
  358. }
  359. }
  360. for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
  361. progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
  362. }
  363. }
  364. - (void)URLSession:(NSURLSession *)session
  365. dataTask:(NSURLSessionDataTask *)dataTask
  366. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  367. completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  368. NSCachedURLResponse *cachedResponse = proposedResponse;
  369. if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
  370. // Prevents caching of responses
  371. cachedResponse = nil;
  372. }
  373. if (completionHandler) {
  374. completionHandler(cachedResponse);
  375. }
  376. }
  377. #pragma mark NSURLSessionTaskDelegate
  378. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  379. @synchronized(self) {
  380. self.dataTask = nil;
  381. __weak typeof(self) weakSelf = self;
  382. dispatch_async(dispatch_get_main_queue(), ^{
  383. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
  384. if (!error) {
  385. [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
  386. }
  387. });
  388. }
  389. if (error) {
  390. [self callCompletionBlocksWithError:error];
  391. } else {
  392. if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
  393. /**
  394. * If you specified to use `NSURLCache`, then the response you get here is what you need.
  395. */
  396. NSData *imageData = [self.imageData copy];
  397. if (imageData) {
  398. UIImage *image = [UIImage sd_imageWithData:imageData];
  399. /** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
  400. * then we should check if the cached data is equal to image data
  401. */
  402. if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
  403. // call completion block with nil
  404. [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
  405. } else {
  406. NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
  407. image = [self scaledImageForKey:key image:image];
  408. BOOL shouldDecode = YES;
  409. // Do not force decoding animated GIFs and WebPs
  410. if (image.images) {
  411. shouldDecode = NO;
  412. } else {
  413. #ifdef SD_WEBP
  414. SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
  415. if (imageFormat == SDImageFormatWebP) {
  416. shouldDecode = NO;
  417. }
  418. #endif
  419. }
  420. if (shouldDecode) {
  421. if (self.shouldDecompressImages) {
  422. if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
  423. #if SD_UIKIT || SD_WATCH
  424. image = [UIImage decodedAndScaledDownImageWithImage:image];
  425. imageData = UIImagePNGRepresentation(image);
  426. #endif
  427. } else {
  428. image = [UIImage decodedImageWithImage:image];
  429. }
  430. }
  431. }
  432. if (CGSizeEqualToSize(image.size, CGSizeZero)) {
  433. [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
  434. } else {
  435. [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
  436. }
  437. }
  438. } else {
  439. [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
  440. }
  441. }
  442. }
  443. [self done];
  444. }
  445. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  446. NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  447. __block NSURLCredential *credential = nil;
  448. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  449. if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
  450. disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  451. } else {
  452. credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  453. disposition = NSURLSessionAuthChallengeUseCredential;
  454. }
  455. } else {
  456. if (challenge.previousFailureCount == 0) {
  457. if (self.credential) {
  458. credential = self.credential;
  459. disposition = NSURLSessionAuthChallengeUseCredential;
  460. } else {
  461. disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
  462. }
  463. } else {
  464. disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
  465. }
  466. }
  467. if (completionHandler) {
  468. completionHandler(disposition, credential);
  469. }
  470. }
  471. #pragma mark Helper methods
  472. #if SD_UIKIT || SD_WATCH
  473. + (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
  474. switch (value) {
  475. case 1:
  476. return UIImageOrientationUp;
  477. case 3:
  478. return UIImageOrientationDown;
  479. case 8:
  480. return UIImageOrientationLeft;
  481. case 6:
  482. return UIImageOrientationRight;
  483. case 2:
  484. return UIImageOrientationUpMirrored;
  485. case 4:
  486. return UIImageOrientationDownMirrored;
  487. case 5:
  488. return UIImageOrientationLeftMirrored;
  489. case 7:
  490. return UIImageOrientationRightMirrored;
  491. default:
  492. return UIImageOrientationUp;
  493. }
  494. }
  495. #endif
  496. - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
  497. return SDScaledImageForKey(key, image);
  498. }
  499. - (BOOL)shouldContinueWhenAppEntersBackground {
  500. return self.options & SDWebImageDownloaderContinueInBackground;
  501. }
  502. - (void)callCompletionBlocksWithError:(nullable NSError *)error {
  503. [self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
  504. }
  505. - (void)callCompletionBlocksWithImage:(nullable UIImage *)image
  506. imageData:(nullable NSData *)imageData
  507. error:(nullable NSError *)error
  508. finished:(BOOL)finished {
  509. NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
  510. dispatch_main_async_safe(^{
  511. for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
  512. completedBlock(image, imageData, error, finished);
  513. }
  514. });
  515. }
  516. @end