SDWebImageDownloader.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
  12. NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
  13. static NSString *const kProgressCallbackKey = @"progress";
  14. static NSString *const kCompletedCallbackKey = @"completed";
  15. @interface SDWebImageDownloader ()
  16. @property (strong, nonatomic) NSOperationQueue *downloadQueue;
  17. @property (weak, nonatomic) NSOperation *lastAddedOperation;
  18. @property (assign, nonatomic) Class operationClass;
  19. @property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
  20. @property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
  21. // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
  22. @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
  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. + (SDWebImageDownloader *)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. - (id)init {
  53. if ((self = [super init])) {
  54. _operationClass = [SDWebImageDownloaderOperation class];
  55. _shouldDecompressImages = YES;
  56. _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
  57. _downloadQueue = [NSOperationQueue new];
  58. _downloadQueue.maxConcurrentOperationCount = 6;
  59. _URLCallbacks = [NSMutableDictionary new];
  60. _HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"image/webp,image/*;q=0.8" forKey:@"Accept"];
  61. _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
  62. _downloadTimeout = 15.0;
  63. }
  64. return self;
  65. }
  66. - (void)dealloc {
  67. [self.downloadQueue cancelAllOperations];
  68. SDDispatchQueueRelease(_barrierQueue);
  69. }
  70. - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
  71. if (value) {
  72. self.HTTPHeaders[field] = value;
  73. }
  74. else {
  75. [self.HTTPHeaders removeObjectForKey:field];
  76. }
  77. }
  78. - (NSString *)valueForHTTPHeaderField:(NSString *)field {
  79. return self.HTTPHeaders[field];
  80. }
  81. - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
  82. _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
  83. }
  84. - (NSUInteger)currentDownloadCount {
  85. return _downloadQueue.operationCount;
  86. }
  87. - (NSInteger)maxConcurrentDownloads {
  88. return _downloadQueue.maxConcurrentOperationCount;
  89. }
  90. - (void)setOperationClass:(Class)operationClass {
  91. _operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
  92. }
  93. - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
  94. __block SDWebImageDownloaderOperation *operation;
  95. __weak SDWebImageDownloader *wself = self;
  96. [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
  97. NSTimeInterval timeoutInterval = wself.downloadTimeout;
  98. if (timeoutInterval == 0.0) {
  99. timeoutInterval = 15.0;
  100. }
  101. // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
  102. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
  103. request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
  104. request.HTTPShouldUsePipelining = YES;
  105. if (wself.headersFilter) {
  106. request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
  107. }
  108. else {
  109. request.allHTTPHeaderFields = wself.HTTPHeaders;
  110. }
  111. operation = [[wself.operationClass alloc] initWithRequest:request
  112. options:options
  113. progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  114. SDWebImageDownloader *sself = wself;
  115. if (!sself) return;
  116. __block NSArray *callbacksForURL;
  117. dispatch_sync(sself.barrierQueue, ^{
  118. callbacksForURL = [sself.URLCallbacks[url] copy];
  119. });
  120. for (NSDictionary *callbacks in callbacksForURL) {
  121. SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
  122. if (callback) callback(receivedSize, expectedSize);
  123. }
  124. }
  125. completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
  126. SDWebImageDownloader *sself = wself;
  127. if (!sself) return;
  128. __block NSArray *callbacksForURL;
  129. dispatch_barrier_sync(sself.barrierQueue, ^{
  130. callbacksForURL = [sself.URLCallbacks[url] copy];
  131. if (finished) {
  132. [sself.URLCallbacks removeObjectForKey:url];
  133. }
  134. });
  135. for (NSDictionary *callbacks in callbacksForURL) {
  136. SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
  137. if (callback) callback(image, data, error, finished);
  138. }
  139. }
  140. cancelled:^{
  141. SDWebImageDownloader *sself = wself;
  142. if (!sself) return;
  143. dispatch_barrier_async(sself.barrierQueue, ^{
  144. [sself.URLCallbacks removeObjectForKey:url];
  145. });
  146. }];
  147. operation.shouldDecompressImages = wself.shouldDecompressImages;
  148. if (wself.username && wself.password) {
  149. operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
  150. }
  151. if (options & SDWebImageDownloaderHighPriority) {
  152. operation.queuePriority = NSOperationQueuePriorityHigh;
  153. } else if (options & SDWebImageDownloaderLowPriority) {
  154. operation.queuePriority = NSOperationQueuePriorityLow;
  155. }
  156. [wself.downloadQueue addOperation:operation];
  157. if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
  158. // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
  159. [wself.lastAddedOperation addDependency:operation];
  160. wself.lastAddedOperation = operation;
  161. }
  162. }];
  163. return operation;
  164. }
  165. - (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
  166. // 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.
  167. if (url == nil) {
  168. if (completedBlock != nil) {
  169. completedBlock(nil, nil, nil, NO);
  170. }
  171. return;
  172. }
  173. dispatch_barrier_sync(self.barrierQueue, ^{
  174. BOOL first = NO;
  175. if (!self.URLCallbacks[url]) {
  176. self.URLCallbacks[url] = [NSMutableArray new];
  177. first = YES;
  178. }
  179. // Handle single download of simultaneous download request for the same URL
  180. NSMutableArray *callbacksForURL = self.URLCallbacks[url];
  181. NSMutableDictionary *callbacks = [NSMutableDictionary new];
  182. if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
  183. if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
  184. [callbacksForURL addObject:callbacks];
  185. self.URLCallbacks[url] = callbacksForURL;
  186. if (first) {
  187. createCallback();
  188. }
  189. });
  190. }
  191. - (void)setSuspended:(BOOL)suspended {
  192. [self.downloadQueue setSuspended:suspended];
  193. }
  194. @end