SDWebImageDownloader.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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 "SDWebImageDownloader.h"
  9. #import "SDWebImageDownloaderConfig.h"
  10. #import "SDWebImageDownloaderOperation.h"
  11. #import "SDWebImageError.h"
  12. #import "SDInternalMacros.h"
  13. NSNotificationName const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
  14. NSNotificationName const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
  15. NSNotificationName const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
  16. NSNotificationName const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
  17. static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
  18. @interface SDWebImageDownloadToken ()
  19. @property (nonatomic, strong, nullable, readwrite) NSURL *url;
  20. @property (nonatomic, strong, nullable, readwrite) NSURLRequest *request;
  21. @property (nonatomic, strong, nullable, readwrite) NSURLResponse *response;
  22. @property (nonatomic, strong, nullable, readwrite) id downloadOperationCancelToken;
  23. @property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperation> *downloadOperation;
  24. @property (nonatomic, weak, nullable) SDWebImageDownloader *downloader;
  25. @property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
  26. - (nonnull instancetype)init NS_UNAVAILABLE;
  27. + (nonnull instancetype)new NS_UNAVAILABLE;
  28. - (nonnull instancetype)initWithDownloadOperation:(nullable NSOperation<SDWebImageDownloaderOperation> *)downloadOperation;
  29. @end
  30. @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
  31. @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
  32. @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
  33. @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
  34. @property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;
  35. @property (strong, nonatomic, nonnull) dispatch_semaphore_t HTTPHeadersLock; // A lock to keep the access to `HTTPHeaders` thread-safe
  36. @property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // A lock to keep the access to `URLOperations` thread-safe
  37. // The session in which data tasks will run
  38. @property (strong, nonatomic) NSURLSession *session;
  39. @end
  40. @implementation SDWebImageDownloader
  41. + (void)initialize {
  42. // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
  43. // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
  44. if (NSClassFromString(@"SDNetworkActivityIndicator")) {
  45. #pragma clang diagnostic push
  46. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  47. id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
  48. #pragma clang diagnostic pop
  49. // Remove observer in case it was previously added.
  50. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
  51. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
  52. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  53. selector:NSSelectorFromString(@"startActivity")
  54. name:SDWebImageDownloadStartNotification object:nil];
  55. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  56. selector:NSSelectorFromString(@"stopActivity")
  57. name:SDWebImageDownloadStopNotification object:nil];
  58. }
  59. }
  60. + (nonnull instancetype)sharedDownloader {
  61. static dispatch_once_t once;
  62. static id instance;
  63. dispatch_once(&once, ^{
  64. instance = [self new];
  65. });
  66. return instance;
  67. }
  68. - (nonnull instancetype)init {
  69. return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
  70. }
  71. - (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
  72. self = [super init];
  73. if (self) {
  74. if (!config) {
  75. config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
  76. }
  77. _config = [config copy];
  78. [_config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) options:0 context:SDWebImageDownloaderContext];
  79. _downloadQueue = [NSOperationQueue new];
  80. _downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
  81. _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
  82. _URLOperations = [NSMutableDictionary new];
  83. NSMutableDictionary<NSString *, NSString *> *headerDictionary = [NSMutableDictionary dictionary];
  84. NSString *userAgent = nil;
  85. #if SD_UIKIT
  86. // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
  87. userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
  88. #elif SD_WATCH
  89. // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
  90. userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
  91. #elif SD_MAC
  92. userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
  93. #endif
  94. if (userAgent) {
  95. if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
  96. NSMutableString *mutableUserAgent = [userAgent mutableCopy];
  97. if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
  98. userAgent = mutableUserAgent;
  99. }
  100. }
  101. headerDictionary[@"User-Agent"] = userAgent;
  102. }
  103. headerDictionary[@"Accept"] = @"image/*;q=0.8";
  104. _HTTPHeaders = headerDictionary;
  105. _HTTPHeadersLock = dispatch_semaphore_create(1);
  106. _operationsLock = dispatch_semaphore_create(1);
  107. NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
  108. if (!sessionConfiguration) {
  109. sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
  110. }
  111. /**
  112. * Create the session for this task
  113. * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  114. * method calls and completion handler calls.
  115. */
  116. _session = [NSURLSession sessionWithConfiguration:sessionConfiguration
  117. delegate:self
  118. delegateQueue:nil];
  119. }
  120. return self;
  121. }
  122. - (void)dealloc {
  123. [self.session invalidateAndCancel];
  124. self.session = nil;
  125. [self.downloadQueue cancelAllOperations];
  126. [self.config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) context:SDWebImageDownloaderContext];
  127. }
  128. - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
  129. if (self == [SDWebImageDownloader sharedDownloader]) {
  130. return;
  131. }
  132. if (cancelPendingOperations) {
  133. [self.session invalidateAndCancel];
  134. } else {
  135. [self.session finishTasksAndInvalidate];
  136. }
  137. }
  138. - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
  139. if (!field) {
  140. return;
  141. }
  142. SD_LOCK(self.HTTPHeadersLock);
  143. [self.HTTPHeaders setValue:value forKey:field];
  144. SD_UNLOCK(self.HTTPHeadersLock);
  145. }
  146. - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
  147. if (!field) {
  148. return nil;
  149. }
  150. SD_LOCK(self.HTTPHeadersLock);
  151. NSString *value = [self.HTTPHeaders objectForKey:field];
  152. SD_UNLOCK(self.HTTPHeadersLock);
  153. return value;
  154. }
  155. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url
  156. completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
  157. return [self downloadImageWithURL:url options:0 progress:nil completed:completedBlock];
  158. }
  159. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url
  160. options:(SDWebImageDownloaderOptions)options
  161. progress:(SDWebImageDownloaderProgressBlock)progressBlock
  162. completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
  163. return [self downloadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock];
  164. }
  165. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
  166. options:(SDWebImageDownloaderOptions)options
  167. context:(nullable SDWebImageContext *)context
  168. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  169. completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
  170. // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
  171. if (url == nil) {
  172. if (completedBlock) {
  173. NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
  174. completedBlock(nil, nil, error, YES);
  175. }
  176. return nil;
  177. }
  178. SD_LOCK(self.operationsLock);
  179. NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
  180. // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
  181. if (!operation || operation.isFinished || operation.isCancelled) {
  182. operation = [self createDownloaderOperationWithUrl:url options:options context:context];
  183. if (!operation) {
  184. SD_UNLOCK(self.operationsLock);
  185. if (completedBlock) {
  186. NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
  187. completedBlock(nil, nil, error, YES);
  188. }
  189. return nil;
  190. }
  191. @weakify(self);
  192. operation.completionBlock = ^{
  193. @strongify(self);
  194. if (!self) {
  195. return;
  196. }
  197. SD_LOCK(self.operationsLock);
  198. [self.URLOperations removeObjectForKey:url];
  199. SD_UNLOCK(self.operationsLock);
  200. };
  201. self.URLOperations[url] = operation;
  202. // Add operation to operation queue only after all configuration done according to Apple's doc.
  203. // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
  204. [self.downloadQueue addOperation:operation];
  205. }
  206. else if (!operation.isExecuting) {
  207. if (options & SDWebImageDownloaderHighPriority) {
  208. operation.queuePriority = NSOperationQueuePriorityHigh;
  209. } else if (options & SDWebImageDownloaderLowPriority) {
  210. operation.queuePriority = NSOperationQueuePriorityLow;
  211. } else {
  212. operation.queuePriority = NSOperationQueuePriorityNormal;
  213. }
  214. }
  215. SD_UNLOCK(self.operationsLock);
  216. id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
  217. SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
  218. token.url = url;
  219. token.request = operation.request;
  220. token.downloadOperationCancelToken = downloadOperationCancelToken;
  221. token.downloader = self;
  222. return token;
  223. }
  224. - (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
  225. options:(SDWebImageDownloaderOptions)options
  226. context:(nullable SDWebImageContext *)context {
  227. NSTimeInterval timeoutInterval = self.config.downloadTimeout;
  228. if (timeoutInterval == 0.0) {
  229. timeoutInterval = 15.0;
  230. }
  231. // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
  232. NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
  233. NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
  234. mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
  235. mutableRequest.HTTPShouldUsePipelining = YES;
  236. SD_LOCK(self.HTTPHeadersLock);
  237. mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
  238. SD_UNLOCK(self.HTTPHeadersLock);
  239. id<SDWebImageDownloaderRequestModifier> requestModifier;
  240. if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
  241. requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
  242. } else {
  243. requestModifier = self.requestModifier;
  244. }
  245. NSURLRequest *request;
  246. if (requestModifier) {
  247. NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
  248. // If modified request is nil, early return
  249. if (!modifiedRequest) {
  250. return nil;
  251. } else {
  252. request = [modifiedRequest copy];
  253. }
  254. } else {
  255. request = [mutableRequest copy];
  256. }
  257. Class operationClass = self.config.operationClass;
  258. if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
  259. // Custom operation class
  260. } else {
  261. operationClass = [SDWebImageDownloaderOperation class];
  262. }
  263. NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
  264. if ([operation respondsToSelector:@selector(setCredential:)]) {
  265. if (self.config.urlCredential) {
  266. operation.credential = self.config.urlCredential;
  267. } else if (self.config.username && self.config.password) {
  268. operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
  269. }
  270. }
  271. if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
  272. operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
  273. }
  274. if (options & SDWebImageDownloaderHighPriority) {
  275. operation.queuePriority = NSOperationQueuePriorityHigh;
  276. } else if (options & SDWebImageDownloaderLowPriority) {
  277. operation.queuePriority = NSOperationQueuePriorityLow;
  278. }
  279. if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
  280. // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
  281. [self.lastAddedOperation addDependency:operation];
  282. self.lastAddedOperation = operation;
  283. }
  284. return operation;
  285. }
  286. - (void)cancel:(nullable SDWebImageDownloadToken *)token {
  287. NSURL *url = token.url;
  288. if (!url) {
  289. return;
  290. }
  291. SD_LOCK(self.operationsLock);
  292. NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
  293. if (operation) {
  294. BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
  295. if (canceled) {
  296. [self.URLOperations removeObjectForKey:url];
  297. }
  298. }
  299. SD_UNLOCK(self.operationsLock);
  300. }
  301. - (void)cancelAllDownloads {
  302. [self.downloadQueue cancelAllOperations];
  303. }
  304. #pragma mark - Properties
  305. - (BOOL)isSuspended {
  306. return self.downloadQueue.isSuspended;
  307. }
  308. - (void)setSuspended:(BOOL)suspended {
  309. self.downloadQueue.suspended = suspended;
  310. }
  311. - (NSUInteger)currentDownloadCount {
  312. return self.downloadQueue.operationCount;
  313. }
  314. - (NSURLSessionConfiguration *)sessionConfiguration {
  315. return self.session.configuration;
  316. }
  317. #pragma mark - KVO
  318. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
  319. if (context == SDWebImageDownloaderContext) {
  320. if ([keyPath isEqualToString:NSStringFromSelector(@selector(maxConcurrentDownloads))]) {
  321. self.downloadQueue.maxConcurrentOperationCount = self.config.maxConcurrentDownloads;
  322. }
  323. } else {
  324. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  325. }
  326. }
  327. #pragma mark Helper methods
  328. - (NSOperation<SDWebImageDownloaderOperation> *)operationWithTask:(NSURLSessionTask *)task {
  329. NSOperation<SDWebImageDownloaderOperation> *returnOperation = nil;
  330. for (NSOperation<SDWebImageDownloaderOperation> *operation in self.downloadQueue.operations) {
  331. if ([operation respondsToSelector:@selector(dataTask)]) {
  332. if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
  333. returnOperation = operation;
  334. break;
  335. }
  336. }
  337. }
  338. return returnOperation;
  339. }
  340. #pragma mark NSURLSessionDataDelegate
  341. - (void)URLSession:(NSURLSession *)session
  342. dataTask:(NSURLSessionDataTask *)dataTask
  343. didReceiveResponse:(NSURLResponse *)response
  344. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  345. // Identify the operation that runs this task and pass it the delegate method
  346. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
  347. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
  348. [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
  349. } else {
  350. if (completionHandler) {
  351. completionHandler(NSURLSessionResponseAllow);
  352. }
  353. }
  354. }
  355. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  356. // Identify the operation that runs this task and pass it the delegate method
  357. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
  358. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
  359. [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
  360. }
  361. }
  362. - (void)URLSession:(NSURLSession *)session
  363. dataTask:(NSURLSessionDataTask *)dataTask
  364. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  365. completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  366. // Identify the operation that runs this task and pass it the delegate method
  367. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:dataTask];
  368. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
  369. [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
  370. } else {
  371. if (completionHandler) {
  372. completionHandler(proposedResponse);
  373. }
  374. }
  375. }
  376. #pragma mark NSURLSessionTaskDelegate
  377. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  378. // Identify the operation that runs this task and pass it the delegate method
  379. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
  380. if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
  381. [dataOperation URLSession:session task:task didCompleteWithError:error];
  382. }
  383. }
  384. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
  385. // Identify the operation that runs this task and pass it the delegate method
  386. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
  387. if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
  388. [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
  389. } else {
  390. if (completionHandler) {
  391. completionHandler(request);
  392. }
  393. }
  394. }
  395. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  396. // Identify the operation that runs this task and pass it the delegate method
  397. NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
  398. if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
  399. [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
  400. } else {
  401. if (completionHandler) {
  402. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  403. }
  404. }
  405. }
  406. @end
  407. @implementation SDWebImageDownloadToken
  408. - (void)dealloc {
  409. [[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadReceiveResponseNotification object:nil];
  410. }
  411. - (instancetype)initWithDownloadOperation:(NSOperation<SDWebImageDownloaderOperation> *)downloadOperation {
  412. self = [super init];
  413. if (self) {
  414. _downloadOperation = downloadOperation;
  415. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
  416. }
  417. return self;
  418. }
  419. - (void)downloadReceiveResponse:(NSNotification *)notification {
  420. NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
  421. if (downloadOperation && downloadOperation == self.downloadOperation) {
  422. self.response = downloadOperation.response;
  423. }
  424. }
  425. - (void)cancel {
  426. @synchronized (self) {
  427. if (self.isCancelled) {
  428. return;
  429. }
  430. self.cancelled = YES;
  431. if (self.downloader) {
  432. // Downloader is alive, cancel token
  433. [self.downloader cancel:self];
  434. } else {
  435. // Downloader is dealloced, only cancel download operation
  436. [self.downloadOperation cancel:self.downloadOperationCancelToken];
  437. }
  438. self.downloadOperationCancelToken = nil;
  439. }
  440. }
  441. @end
  442. @implementation SDWebImageDownloader (SDImageLoader)
  443. - (BOOL)canRequestImageForURL:(NSURL *)url {
  444. if (!url) {
  445. return NO;
  446. }
  447. // Always pass YES to let URLSession or custom download operation to determine
  448. return YES;
  449. }
  450. - (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
  451. UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];
  452. SDWebImageDownloaderOptions downloaderOptions = 0;
  453. if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
  454. if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
  455. if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
  456. if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
  457. if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
  458. if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
  459. if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
  460. if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
  461. if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
  462. if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
  463. if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
  464. if (cachedImage && options & SDWebImageRefreshCached) {
  465. // force progressive off if image already cached but forced refreshing
  466. downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
  467. // ignore image read from NSURLCache if image if cached but force refreshing
  468. downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
  469. }
  470. return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
  471. }
  472. - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error {
  473. BOOL shouldBlockFailedURL;
  474. // Filter the error domain and check error codes
  475. if ([error.domain isEqualToString:SDWebImageErrorDomain]) {
  476. shouldBlockFailedURL = ( error.code == SDWebImageErrorInvalidURL
  477. || error.code == SDWebImageErrorBadImageData);
  478. } else if ([error.domain isEqualToString:NSURLErrorDomain]) {
  479. shouldBlockFailedURL = ( error.code != NSURLErrorNotConnectedToInternet
  480. && error.code != NSURLErrorCancelled
  481. && error.code != NSURLErrorTimedOut
  482. && error.code != NSURLErrorInternationalRoamingOff
  483. && error.code != NSURLErrorDataNotAllowed
  484. && error.code != NSURLErrorCannotFindHost
  485. && error.code != NSURLErrorCannotConnectToHost
  486. && error.code != NSURLErrorNetworkConnectionLost);
  487. } else {
  488. shouldBlockFailedURL = NO;
  489. }
  490. return shouldBlockFailedURL;
  491. }
  492. @end