SDWebImageDownloader.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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. #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
  11. #define UNLOCK(lock) dispatch_semaphore_signal(lock);
  12. @interface SDWebImageDownloadToken ()
  13. @property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;
  14. @end
  15. @implementation SDWebImageDownloadToken
  16. - (void)cancel {
  17. if (self.downloadOperation) {
  18. SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
  19. if (cancelToken) {
  20. [self.downloadOperation cancel:cancelToken];
  21. }
  22. }
  23. }
  24. @end
  25. @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
  26. @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
  27. @property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
  28. @property (assign, nonatomic, nullable) Class operationClass;
  29. @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperationInterface> *> *URLOperations;
  30. @property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
  31. @property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
  32. @property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe
  33. // The session in which data tasks will run
  34. @property (strong, nonatomic) NSURLSession *session;
  35. @end
  36. @implementation SDWebImageDownloader
  37. + (void)initialize {
  38. // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
  39. // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
  40. if (NSClassFromString(@"SDNetworkActivityIndicator")) {
  41. #pragma clang diagnostic push
  42. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  43. id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
  44. #pragma clang diagnostic pop
  45. // Remove observer in case it was previously added.
  46. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
  47. [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
  48. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  49. selector:NSSelectorFromString(@"startActivity")
  50. name:SDWebImageDownloadStartNotification object:nil];
  51. [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
  52. selector:NSSelectorFromString(@"stopActivity")
  53. name:SDWebImageDownloadStopNotification object:nil];
  54. }
  55. }
  56. + (nonnull instancetype)sharedDownloader {
  57. static dispatch_once_t once;
  58. static id instance;
  59. dispatch_once(&once, ^{
  60. instance = [self new];
  61. });
  62. return instance;
  63. }
  64. - (nonnull instancetype)init {
  65. return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
  66. }
  67. - (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
  68. if ((self = [super init])) {
  69. _operationClass = [SDWebImageDownloaderOperation class];
  70. _shouldDecompressImages = YES;
  71. _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
  72. _downloadQueue = [NSOperationQueue new];
  73. _downloadQueue.maxConcurrentOperationCount = 6;
  74. _downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
  75. _URLOperations = [NSMutableDictionary new];
  76. SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
  77. NSString *userAgent = nil;
  78. #if SD_UIKIT
  79. // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
  80. 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]];
  81. #elif SD_WATCH
  82. // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
  83. 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]];
  84. #elif SD_MAC
  85. 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]];
  86. #endif
  87. if (userAgent) {
  88. if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
  89. NSMutableString *mutableUserAgent = [userAgent mutableCopy];
  90. if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
  91. userAgent = mutableUserAgent;
  92. }
  93. }
  94. headerDictionary[@"User-Agent"] = userAgent;
  95. }
  96. #ifdef SD_WEBP
  97. headerDictionary[@"Accept"] = @"image/webp,image/*;q=0.8";
  98. #else
  99. headerDictionary[@"Accept"] = @"image/*;q=0.8";
  100. #endif
  101. _HTTPHeaders = headerDictionary;
  102. _operationsLock = dispatch_semaphore_create(1);
  103. _headersLock = dispatch_semaphore_create(1);
  104. _downloadTimeout = 15.0;
  105. [self createNewSessionWithConfiguration:sessionConfiguration];
  106. }
  107. return self;
  108. }
  109. - (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
  110. [self cancelAllDownloads];
  111. if (self.session) {
  112. [self.session invalidateAndCancel];
  113. }
  114. sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
  115. /**
  116. * Create the session for this task
  117. * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  118. * method calls and completion handler calls.
  119. */
  120. self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
  121. delegate:self
  122. delegateQueue:nil];
  123. }
  124. - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
  125. if (self == [SDWebImageDownloader sharedDownloader]) {
  126. return;
  127. }
  128. if (cancelPendingOperations) {
  129. [self.session invalidateAndCancel];
  130. } else {
  131. [self.session finishTasksAndInvalidate];
  132. }
  133. }
  134. - (void)dealloc {
  135. [self.session invalidateAndCancel];
  136. self.session = nil;
  137. [self.downloadQueue cancelAllOperations];
  138. }
  139. - (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
  140. LOCK(self.headersLock);
  141. if (value) {
  142. self.HTTPHeaders[field] = value;
  143. } else {
  144. [self.HTTPHeaders removeObjectForKey:field];
  145. }
  146. UNLOCK(self.headersLock);
  147. }
  148. - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
  149. if (!field) {
  150. return nil;
  151. }
  152. return [[self allHTTPHeaderFields] objectForKey:field];
  153. }
  154. - (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields {
  155. LOCK(self.headersLock);
  156. SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
  157. UNLOCK(self.headersLock);
  158. return allHTTPHeaderFields;
  159. }
  160. - (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
  161. _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
  162. }
  163. - (NSUInteger)currentDownloadCount {
  164. return _downloadQueue.operationCount;
  165. }
  166. - (NSInteger)maxConcurrentDownloads {
  167. return _downloadQueue.maxConcurrentOperationCount;
  168. }
  169. - (NSURLSessionConfiguration *)sessionConfiguration {
  170. return self.session.configuration;
  171. }
  172. - (void)setOperationClass:(nullable Class)operationClass {
  173. if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
  174. _operationClass = operationClass;
  175. } else {
  176. _operationClass = [SDWebImageDownloaderOperation class];
  177. }
  178. }
  179. - (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
  180. options:(SDWebImageDownloaderOptions)options {
  181. NSTimeInterval timeoutInterval = self.downloadTimeout;
  182. if (timeoutInterval == 0.0) {
  183. timeoutInterval = 15.0;
  184. }
  185. // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
  186. NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
  187. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
  188. cachePolicy:cachePolicy
  189. timeoutInterval:timeoutInterval];
  190. request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
  191. request.HTTPShouldUsePipelining = YES;
  192. if (self.headersFilter) {
  193. request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
  194. }
  195. else {
  196. request.allHTTPHeaderFields = [self allHTTPHeaderFields];
  197. }
  198. NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
  199. operation.shouldDecompressImages = self.shouldDecompressImages;
  200. if (self.urlCredential) {
  201. operation.credential = self.urlCredential;
  202. } else if (self.username && self.password) {
  203. operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
  204. }
  205. if (options & SDWebImageDownloaderHighPriority) {
  206. operation.queuePriority = NSOperationQueuePriorityHigh;
  207. } else if (options & SDWebImageDownloaderLowPriority) {
  208. operation.queuePriority = NSOperationQueuePriorityLow;
  209. }
  210. if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
  211. // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
  212. [self.lastAddedOperation addDependency:operation];
  213. self.lastAddedOperation = operation;
  214. }
  215. return operation;
  216. }
  217. - (void)cancel:(nullable SDWebImageDownloadToken *)token {
  218. NSURL *url = token.url;
  219. if (!url) {
  220. return;
  221. }
  222. LOCK(self.operationsLock);
  223. NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
  224. if (operation) {
  225. BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
  226. if (canceled) {
  227. [self.URLOperations removeObjectForKey:url];
  228. }
  229. }
  230. UNLOCK(self.operationsLock);
  231. }
  232. - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
  233. options:(SDWebImageDownloaderOptions)options
  234. progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
  235. completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
  236. // 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.
  237. if (url == nil) {
  238. if (completedBlock != nil) {
  239. completedBlock(nil, nil, nil, NO);
  240. }
  241. return nil;
  242. }
  243. LOCK(self.operationsLock);
  244. NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
  245. // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
  246. if (!operation || operation.isFinished || operation.isCancelled) {
  247. operation = [self createDownloaderOperationWithUrl:url options:options];
  248. __weak typeof(self) wself = self;
  249. operation.completionBlock = ^{
  250. __strong typeof(wself) sself = wself;
  251. if (!sself) {
  252. return;
  253. }
  254. LOCK(sself.operationsLock);
  255. [sself.URLOperations removeObjectForKey:url];
  256. UNLOCK(sself.operationsLock);
  257. };
  258. [self.URLOperations setObject:operation forKey:url];
  259. // Add operation to operation queue only after all configuration done according to Apple's doc.
  260. // `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
  261. [self.downloadQueue addOperation:operation];
  262. }
  263. else if (!operation.isExecuting) {
  264. if (options & SDWebImageDownloaderHighPriority) {
  265. operation.queuePriority = NSOperationQueuePriorityHigh;
  266. } else if (options & SDWebImageDownloaderLowPriority) {
  267. operation.queuePriority = NSOperationQueuePriorityLow;
  268. } else {
  269. operation.queuePriority = NSOperationQueuePriorityNormal;
  270. }
  271. }
  272. UNLOCK(self.operationsLock);
  273. id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
  274. SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
  275. token.downloadOperation = operation;
  276. token.url = url;
  277. token.downloadOperationCancelToken = downloadOperationCancelToken;
  278. return token;
  279. }
  280. - (void)setSuspended:(BOOL)suspended {
  281. self.downloadQueue.suspended = suspended;
  282. }
  283. - (void)cancelAllDownloads {
  284. [self.downloadQueue cancelAllOperations];
  285. }
  286. #pragma mark Helper methods
  287. - (NSOperation<SDWebImageDownloaderOperationInterface> *)operationWithTask:(NSURLSessionTask *)task {
  288. NSOperation<SDWebImageDownloaderOperationInterface> *returnOperation = nil;
  289. for (NSOperation<SDWebImageDownloaderOperationInterface> *operation in self.downloadQueue.operations) {
  290. if ([operation respondsToSelector:@selector(dataTask)]) {
  291. if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
  292. returnOperation = operation;
  293. break;
  294. }
  295. }
  296. }
  297. return returnOperation;
  298. }
  299. #pragma mark NSURLSessionDataDelegate
  300. - (void)URLSession:(NSURLSession *)session
  301. dataTask:(NSURLSessionDataTask *)dataTask
  302. didReceiveResponse:(NSURLResponse *)response
  303. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  304. // Identify the operation that runs this task and pass it the delegate method
  305. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:dataTask];
  306. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
  307. [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
  308. } else {
  309. if (completionHandler) {
  310. completionHandler(NSURLSessionResponseAllow);
  311. }
  312. }
  313. }
  314. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  315. // Identify the operation that runs this task and pass it the delegate method
  316. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:dataTask];
  317. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
  318. [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
  319. }
  320. }
  321. - (void)URLSession:(NSURLSession *)session
  322. dataTask:(NSURLSessionDataTask *)dataTask
  323. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  324. completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  325. // Identify the operation that runs this task and pass it the delegate method
  326. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:dataTask];
  327. if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
  328. [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
  329. } else {
  330. if (completionHandler) {
  331. completionHandler(proposedResponse);
  332. }
  333. }
  334. }
  335. #pragma mark NSURLSessionTaskDelegate
  336. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  337. // Identify the operation that runs this task and pass it the delegate method
  338. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
  339. if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
  340. [dataOperation URLSession:session task:task didCompleteWithError:error];
  341. }
  342. }
  343. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
  344. // Identify the operation that runs this task and pass it the delegate method
  345. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
  346. if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
  347. [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
  348. } else {
  349. if (completionHandler) {
  350. completionHandler(request);
  351. }
  352. }
  353. }
  354. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  355. // Identify the operation that runs this task and pass it the delegate method
  356. NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
  357. if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
  358. [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
  359. } else {
  360. if (completionHandler) {
  361. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  362. }
  363. }
  364. }
  365. @end