SDWebImageDownloader.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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 "SDWebImageDownloaderOperation.h"
  10. #import <ImageIO/ImageIO.h>
  11. @implementation SDWebImageDownloadToken
  12. @end
  13. @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
  14. @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
  15. @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
  16. @property (assign, nonatomic, nullable) Class operationClass;
  17. @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
  18. @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
  19. // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
  20. @property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
  21. // The session in which data tasks will run
  22. @property (strong, nonatomic) NSURLSession *session;
  23. @end
  24. @implementation SDWebImageDownloader
  25. + (void)initialize {
  26. // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
  27. // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
  28. if (NSClassFromString(@"SDNetworkActivityIndicator")) {
  29. #pragma clang diagnostic push
  30. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  31. id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
  32. #pragma clang diagnostic pop
  33. // Remove observer in case it was previously added.
  34. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
  35. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
  36. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  37. selector:NSSelectorFromString(@"startActivity")
  38. name:SDWebImageDownloadStartNotification object:nil];
  39. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  40. selector:NSSelectorFromString(@"stopActivity")
  41. name:SDWebImageDownloadStopNotification object:nil];
  42. }
  43. }
  44. + (nonnull instancetype)sharedDownloader {
  45. static dispatch_once_t once;
  46. static id instance;
  47. dispatch_once(&once, ^{
  48. instance = [self new];
  49. });
  50. return instance;
  51. }
  52. - (nonnull instancetype)init {
  53. return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  54. }
  55. - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
  56. if ((self = [super init])) {
  57. _operationClass = [SDWebImageDownloaderOperation class];
  58. _shouldDecompressImages = YES;
  59. _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
  60. _downloadQueue = [NSOperationQueue new];
  61. _downloadQueue.maxConcurrentOperationCount = 6;
  62. _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
  63. _URLOperations = [NSMutableDictionary new];
  64. #ifdef SD_WEBP
  65. _HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
  66. #else
  67. _HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
  68. #endif
  69. _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
  70. _downloadTimeout = 15.0;
  71. [self createNewSessionWithConfiguration:sessionConfiguration];
  72. }
  73. return self;
  74. }
  75. - (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
  76. [self cancelAllDownloads];
  77. if (self.session) {
  78. [self.session invalidateAndCancel];
  79. }
  80. sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
  81. /**
  82. * Create the session for this task
  83. * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  84. * method calls and completion handler calls.
  85. */
  86. self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
  87. delegate:self
  88. delegateQueue:nil];
  89. }
  90. - (void)dealloc {
  91. [self.session invalidateAndCancel];
  92. self.session = nil;
  93. [self.downloadQueue cancelAllOperations];
  94. SDDispatchQueueRelease(_barrierQueue);
  95. }
  96. - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
  97. if (value) {
  98. self.HTTPHeaders[field] = value;
  99. } else {
  100. [self.HTTPHeaders removeObjectForKey:field];
  101. }
  102. }
  103. - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
  104. return self.HTTPHeaders[field];
  105. }
  106. - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
  107. _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
  108. }
  109. - (NSUInteger)currentDownloadCount {
  110. return _downloadQueue.operationCount;
  111. }
  112. - (NSInteger)maxConcurrentDownloads {
  113. return _downloadQueue.maxConcurrentOperationCount;
  114. }
  115. - (NSURLSessionConfiguration *)sessionConfiguration {
  116. return self.session.configuration;
  117. }
  118. - (void)setOperationClass:(nullable Class)operationClass {
  119. if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
  120. _operationClass = operationClass;
  121. } else {
  122. _operationClass = [SDWebImageDownloaderOperation class];
  123. }
  124. }
  125. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
  126. options:(SDWebImageDownloaderOptions)options
  127. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  128. completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
  129. __weak SDWebImageDownloader *wself = self;
  130. return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
  131. __strong __typeof (wself) sself = wself;
  132. NSTimeInterval timeoutInterval = sself.downloadTimeout;
  133. if (timeoutInterval == 0.0) {
  134. timeoutInterval = 15.0;
  135. }
  136. // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
  137. NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
  138. if (options & SDWebImageDownloaderUseNSURLCache) {
  139. if (options & SDWebImageDownloaderIgnoreCachedResponse) {
  140. cachePolicy = NSURLRequestReturnCacheDataDontLoad;
  141. } else {
  142. cachePolicy = NSURLRequestUseProtocolCachePolicy;
  143. }
  144. }
  145. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
  146. request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
  147. request.HTTPShouldUsePipelining = YES;
  148. if (sself.headersFilter) {
  149. request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
  150. }
  151. else {
  152. request.allHTTPHeaderFields = sself.HTTPHeaders;
  153. }
  154. SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
  155. operation.shouldDecompressImages = sself.shouldDecompressImages;
  156. if (sself.urlCredential) {
  157. operation.credential = sself.urlCredential;
  158. } else if (sself.username && sself.password) {
  159. operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
  160. }
  161. if (options & SDWebImageDownloaderHighPriority) {
  162. operation.queuePriority = NSOperationQueuePriorityHigh;
  163. } else if (options & SDWebImageDownloaderLowPriority) {
  164. operation.queuePriority = NSOperationQueuePriorityLow;
  165. }
  166. [sself.downloadQueue addOperation:operation];
  167. if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
  168. // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
  169. [sself.lastAddedOperation addDependency:operation];
  170. sself.lastAddedOperation = operation;
  171. }
  172. return operation;
  173. }];
  174. }
  175. - (void)cancel:(nullable SDWebImageDownloadToken *)token {
  176. dispatch_barrier_async(self.barrierQueue, ^{
  177. SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
  178. BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
  179. if (canceled) {
  180. [self.URLOperations removeObjectForKey:token.url];
  181. }
  182. });
  183. }
  184. - (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
  185. completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
  186. forURL:(nullable NSURL *)url
  187. createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
  188. // 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.
  189. if (url == nil) {
  190. if (completedBlock != nil) {
  191. completedBlock(nil, nil, nil, NO);
  192. }
  193. return nil;
  194. }
  195. __block SDWebImageDownloadToken *token = nil;
  196. dispatch_barrier_sync(self.barrierQueue, ^{
  197. SDWebImageDownloaderOperation *operation = self.URLOperations[url];
  198. if (!operation) {
  199. operation = createCallback();
  200. self.URLOperations[url] = operation;
  201. __weak SDWebImageDownloaderOperation *woperation = operation;
  202. operation.completionBlock = ^{
  203. dispatch_barrier_sync(self.barrierQueue, ^{
  204. SDWebImageDownloaderOperation *soperation = woperation;
  205. if (!soperation) return;
  206. if (self.URLOperations[url] == soperation) {
  207. [self.URLOperations removeObjectForKey:url];
  208. };
  209. });
  210. };
  211. }
  212. id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
  213. token = [SDWebImageDownloadToken new];
  214. token.url = url;
  215. token.downloadOperationCancelToken = downloadOperationCancelToken;
  216. });
  217. return token;
  218. }
  219. - (void)setSuspended:(BOOL)suspended {
  220. self.downloadQueue.suspended = suspended;
  221. }
  222. - (void)cancelAllDownloads {
  223. [self.downloadQueue cancelAllOperations];
  224. }
  225. #pragma mark Helper methods
  226. - (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
  227. SDWebImageDownloaderOperation *returnOperation = nil;
  228. for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
  229. if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
  230. returnOperation = operation;
  231. break;
  232. }
  233. }
  234. return returnOperation;
  235. }
  236. #pragma mark NSURLSessionDataDelegate
  237. - (void)URLSession:(NSURLSession *)session
  238. dataTask:(NSURLSessionDataTask *)dataTask
  239. didReceiveResponse:(NSURLResponse *)response
  240. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  241. // Identify the operation that runs this task and pass it the delegate method
  242. SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  243. [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
  244. }
  245. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  246. // Identify the operation that runs this task and pass it the delegate method
  247. SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  248. [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
  249. }
  250. - (void)URLSession:(NSURLSession *)session
  251. dataTask:(NSURLSessionDataTask *)dataTask
  252. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  253. completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  254. // Identify the operation that runs this task and pass it the delegate method
  255. SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  256. [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
  257. }
  258. #pragma mark NSURLSessionTaskDelegate
  259. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  260. // Identify the operation that runs this task and pass it the delegate method
  261. SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
  262. [dataOperation URLSession:session task:task didCompleteWithError:error];
  263. }
  264. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
  265. completionHandler(request);
  266. }
  267. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  268. // Identify the operation that runs this task and pass it the delegate method
  269. SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
  270. [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
  271. }
  272. @end