SDWebImageDownloader.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 (strong, nonatomic) NSMutableDictionary *URLCallbacks;
  19. @property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
  20. // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
  21. @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
  22. @end
  23. @implementation SDWebImageDownloader
  24. + (void)initialize {
  25. // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
  26. // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
  27. if (NSClassFromString(@"SDNetworkActivityIndicator")) {
  28. #pragma clang diagnostic push
  29. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  30. id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
  31. #pragma clang diagnostic pop
  32. // Remove observer in case it was previously added.
  33. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
  34. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
  35. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  36. selector:NSSelectorFromString(@"startActivity")
  37. name:SDWebImageDownloadStartNotification object:nil];
  38. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  39. selector:NSSelectorFromString(@"stopActivity")
  40. name:SDWebImageDownloadStopNotification object:nil];
  41. }
  42. }
  43. + (SDWebImageDownloader *)sharedDownloader {
  44. static dispatch_once_t once;
  45. static id instance;
  46. dispatch_once(&once, ^{
  47. instance = [self new];
  48. });
  49. return instance;
  50. }
  51. - (id)init {
  52. if ((self = [super init])) {
  53. _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
  54. _downloadQueue = [NSOperationQueue new];
  55. _downloadQueue.maxConcurrentOperationCount = 2;
  56. _URLCallbacks = [NSMutableDictionary new];
  57. _HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"image/webp,image/*;q=0.8" forKey:@"Accept"];
  58. _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
  59. _downloadTimeout = 15.0;
  60. }
  61. return self;
  62. }
  63. - (void)dealloc {
  64. [self.downloadQueue cancelAllOperations];
  65. SDDispatchQueueRelease(_barrierQueue);
  66. }
  67. - (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
  68. if (value) {
  69. self.HTTPHeaders[field] = value;
  70. }
  71. else {
  72. [self.HTTPHeaders removeObjectForKey:field];
  73. }
  74. }
  75. - (NSString *)valueForHTTPHeaderField:(NSString *)field {
  76. return self.HTTPHeaders[field];
  77. }
  78. - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
  79. _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
  80. }
  81. - (NSUInteger)currentDownloadCount {
  82. return _downloadQueue.operationCount;
  83. }
  84. - (NSInteger)maxConcurrentDownloads {
  85. return _downloadQueue.maxConcurrentOperationCount;
  86. }
  87. - (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
  88. __block SDWebImageDownloaderOperation *operation;
  89. __weak SDWebImageDownloader *wself = self;
  90. [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
  91. NSTimeInterval timeoutInterval = wself.downloadTimeout;
  92. if (timeoutInterval == 0.0) {
  93. timeoutInterval = 15.0;
  94. }
  95. // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
  96. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
  97. request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
  98. request.HTTPShouldUsePipelining = YES;
  99. if (wself.headersFilter) {
  100. request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
  101. }
  102. else {
  103. request.allHTTPHeaderFields = wself.HTTPHeaders;
  104. }
  105. operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request
  106. options:options
  107. progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  108. SDWebImageDownloader *sself = wself;
  109. if (!sself) return;
  110. NSArray *callbacksForURL = [sself callbacksForURL:url];
  111. for (NSDictionary *callbacks in callbacksForURL) {
  112. SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
  113. if (callback) callback(receivedSize, expectedSize);
  114. }
  115. }
  116. completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
  117. SDWebImageDownloader *sself = wself;
  118. if (!sself) return;
  119. NSArray *callbacksForURL = [sself callbacksForURL:url];
  120. if (finished) {
  121. [sself removeCallbacksForURL:url];
  122. }
  123. for (NSDictionary *callbacks in callbacksForURL) {
  124. SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
  125. if (callback) callback(image, data, error, finished);
  126. }
  127. }
  128. cancelled:^{
  129. SDWebImageDownloader *sself = wself;
  130. if (!sself) return;
  131. [sself removeCallbacksForURL:url];
  132. }];
  133. if (wself.username && wself.password) {
  134. operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
  135. }
  136. if (options & SDWebImageDownloaderHighPriority) {
  137. operation.queuePriority = NSOperationQueuePriorityHigh;
  138. } else if (options & SDWebImageDownloaderLowPriority) {
  139. operation.queuePriority = NSOperationQueuePriorityLow;
  140. }
  141. [wself.downloadQueue addOperation:operation];
  142. if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
  143. // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
  144. [wself.lastAddedOperation addDependency:operation];
  145. wself.lastAddedOperation = operation;
  146. }
  147. }];
  148. return operation;
  149. }
  150. - (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock andCompletedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
  151. // 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.
  152. if (url == nil) {
  153. if (completedBlock != nil) {
  154. completedBlock(nil, nil, nil, NO);
  155. }
  156. return;
  157. }
  158. dispatch_barrier_sync(self.barrierQueue, ^{
  159. BOOL first = NO;
  160. if (!self.URLCallbacks[url]) {
  161. self.URLCallbacks[url] = [NSMutableArray new];
  162. first = YES;
  163. }
  164. // Handle single download of simultaneous download request for the same URL
  165. NSMutableArray *callbacksForURL = self.URLCallbacks[url];
  166. NSMutableDictionary *callbacks = [NSMutableDictionary new];
  167. if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
  168. if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
  169. [callbacksForURL addObject:callbacks];
  170. self.URLCallbacks[url] = callbacksForURL;
  171. if (first) {
  172. createCallback();
  173. }
  174. });
  175. }
  176. - (NSArray *)callbacksForURL:(NSURL *)url {
  177. __block NSArray *callbacksForURL;
  178. dispatch_sync(self.barrierQueue, ^{
  179. callbacksForURL = self.URLCallbacks[url];
  180. });
  181. return [callbacksForURL copy];
  182. }
  183. - (void)removeCallbacksForURL:(NSURL *)url {
  184. dispatch_barrier_async(self.barrierQueue, ^{
  185. [self.URLCallbacks removeObjectForKey:url];
  186. });
  187. }
  188. - (void)setSuspended:(BOOL)suspended {
  189. [self.downloadQueue setSuspended:suspended];
  190. }
  191. @end