SDWebImageManager.m 21 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 "SDWebImageManager.h"
  9. #import "SDImageCache.h"
  10. #import "SDWebImageDownloader.h"
  11. #import "UIImage+Metadata.h"
  12. #import "SDWebImageError.h"
  13. #import "SDInternalMacros.h"
  14. static id<SDImageCache> _defaultImageCache;
  15. static id<SDImageLoader> _defaultImageLoader;
  16. @interface SDWebImageCombinedOperation ()
  17. @property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
  18. @property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> loaderOperation;
  19. @property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> cacheOperation;
  20. @property (weak, nonatomic, nullable) SDWebImageManager *manager;
  21. @end
  22. @interface SDWebImageManager ()
  23. @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache;
  24. @property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader;
  25. @property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs;
  26. @property (strong, nonatomic, nonnull) dispatch_semaphore_t failedURLsLock; // a lock to keep the access to `failedURLs` thread-safe
  27. @property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations;
  28. @property (strong, nonatomic, nonnull) dispatch_semaphore_t runningOperationsLock; // a lock to keep the access to `runningOperations` thread-safe
  29. @end
  30. @implementation SDWebImageManager
  31. + (id<SDImageCache>)defaultImageCache {
  32. return _defaultImageCache;
  33. }
  34. + (void)setDefaultImageCache:(id<SDImageCache>)defaultImageCache {
  35. if (defaultImageCache && ![defaultImageCache conformsToProtocol:@protocol(SDImageCache)]) {
  36. return;
  37. }
  38. _defaultImageCache = defaultImageCache;
  39. }
  40. + (id<SDImageLoader>)defaultImageLoader {
  41. return _defaultImageLoader;
  42. }
  43. + (void)setDefaultImageLoader:(id<SDImageLoader>)defaultImageLoader {
  44. if (defaultImageLoader && ![defaultImageLoader conformsToProtocol:@protocol(SDImageLoader)]) {
  45. return;
  46. }
  47. _defaultImageLoader = defaultImageLoader;
  48. }
  49. + (nonnull instancetype)sharedManager {
  50. static dispatch_once_t once;
  51. static id instance;
  52. dispatch_once(&once, ^{
  53. instance = [self new];
  54. });
  55. return instance;
  56. }
  57. - (nonnull instancetype)init {
  58. id<SDImageCache> cache = [[self class] defaultImageCache];
  59. if (!cache) {
  60. cache = [SDImageCache sharedImageCache];
  61. }
  62. id<SDImageLoader> loader = [[self class] defaultImageLoader];
  63. if (!loader) {
  64. loader = [SDWebImageDownloader sharedDownloader];
  65. }
  66. return [self initWithCache:cache loader:loader];
  67. }
  68. - (nonnull instancetype)initWithCache:(nonnull id<SDImageCache>)cache loader:(nonnull id<SDImageLoader>)loader {
  69. if ((self = [super init])) {
  70. _imageCache = cache;
  71. _imageLoader = loader;
  72. _failedURLs = [NSMutableSet new];
  73. _failedURLsLock = dispatch_semaphore_create(1);
  74. _runningOperations = [NSMutableSet new];
  75. _runningOperationsLock = dispatch_semaphore_create(1);
  76. }
  77. return self;
  78. }
  79. - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
  80. return [self cacheKeyForURL:url cacheKeyFilter:self.cacheKeyFilter];
  81. }
  82. - (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id<SDWebImageCacheKeyFilter>)cacheKeyFilter {
  83. if (!url) {
  84. return @"";
  85. }
  86. if (cacheKeyFilter) {
  87. return [cacheKeyFilter cacheKeyForURL:url];
  88. } else {
  89. return url.absoluteString;
  90. }
  91. }
  92. - (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
  93. return [self loadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock];
  94. }
  95. - (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
  96. options:(SDWebImageOptions)options
  97. context:(nullable SDWebImageContext *)context
  98. progress:(nullable SDImageLoaderProgressBlock)progressBlock
  99. completed:(nonnull SDInternalCompletionBlock)completedBlock {
  100. // Invoking this method without a completedBlock is pointless
  101. NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
  102. // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
  103. // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
  104. if ([url isKindOfClass:NSString.class]) {
  105. url = [NSURL URLWithString:(NSString *)url];
  106. }
  107. // Prevents app crashing on argument type error like sending NSNull instead of NSURL
  108. if (![url isKindOfClass:NSURL.class]) {
  109. url = nil;
  110. }
  111. SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
  112. operation.manager = self;
  113. BOOL isFailedUrl = NO;
  114. if (url) {
  115. SD_LOCK(self.failedURLsLock);
  116. isFailedUrl = [self.failedURLs containsObject:url];
  117. SD_UNLOCK(self.failedURLsLock);
  118. }
  119. if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
  120. [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
  121. return operation;
  122. }
  123. SD_LOCK(self.runningOperationsLock);
  124. [self.runningOperations addObject:operation];
  125. SD_UNLOCK(self.runningOperationsLock);
  126. // Preprocess the context arg to provide the default value from manager
  127. context = [self processedContextWithContext:context];
  128. // Start the entry to load image from cache
  129. [self callCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];
  130. return operation;
  131. }
  132. - (void)cancelAll {
  133. SD_LOCK(self.runningOperationsLock);
  134. NSSet<SDWebImageCombinedOperation *> *copiedOperations = [self.runningOperations copy];
  135. SD_UNLOCK(self.runningOperationsLock);
  136. [copiedOperations makeObjectsPerformSelector:@selector(cancel)]; // This will call `safelyRemoveOperationFromRunning:` and remove from the array
  137. }
  138. - (BOOL)isRunning {
  139. BOOL isRunning = NO;
  140. SD_LOCK(self.runningOperationsLock);
  141. isRunning = (self.runningOperations.count > 0);
  142. SD_UNLOCK(self.runningOperationsLock);
  143. return isRunning;
  144. }
  145. #pragma mark - Private
  146. // Query cache process
  147. - (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
  148. url:(nonnull NSURL *)url
  149. options:(SDWebImageOptions)options
  150. context:(nullable SDWebImageContext *)context
  151. progress:(nullable SDImageLoaderProgressBlock)progressBlock
  152. completed:(nullable SDInternalCompletionBlock)completedBlock {
  153. // Check whether we should query cache
  154. BOOL shouldQueryCache = (options & SDWebImageFromLoaderOnly) == 0;
  155. if (shouldQueryCache) {
  156. id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
  157. NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
  158. @weakify(operation);
  159. operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
  160. @strongify(operation);
  161. if (!operation || operation.isCancelled) {
  162. [self safelyRemoveOperationFromRunning:operation];
  163. return;
  164. }
  165. // Continue download process
  166. [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
  167. }];
  168. } else {
  169. // Continue download process
  170. [self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
  171. }
  172. }
  173. // Download process
  174. - (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
  175. url:(nonnull NSURL *)url
  176. options:(SDWebImageOptions)options
  177. context:(SDWebImageContext *)context
  178. cachedImage:(nullable UIImage *)cachedImage
  179. cachedData:(nullable NSData *)cachedData
  180. cacheType:(SDImageCacheType)cacheType
  181. progress:(nullable SDImageLoaderProgressBlock)progressBlock
  182. completed:(nullable SDInternalCompletionBlock)completedBlock {
  183. // Check whether we should download image from network
  184. BOOL shouldDownload = (options & SDWebImageFromCacheOnly) == 0;
  185. shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
  186. shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
  187. shouldDownload &= [self.imageLoader canRequestImageForURL:url];
  188. if (shouldDownload) {
  189. if (cachedImage && options & SDWebImageRefreshCached) {
  190. // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
  191. // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
  192. [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
  193. // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
  194. SDWebImageMutableContext *mutableContext;
  195. if (context) {
  196. mutableContext = [context mutableCopy];
  197. } else {
  198. mutableContext = [NSMutableDictionary dictionary];
  199. }
  200. mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
  201. context = [mutableContext copy];
  202. }
  203. // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
  204. @weakify(operation);
  205. operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
  206. @strongify(operation);
  207. if (!operation || operation.isCancelled) {
  208. // Do nothing if the operation was cancelled
  209. // See #699 for more details
  210. // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
  211. } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
  212. // Image refresh hit the NSURLCache cache, do not call the completion block
  213. } else if (error) {
  214. [self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
  215. BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
  216. if (shouldBlockFailedURL) {
  217. SD_LOCK(self.failedURLsLock);
  218. [self.failedURLs addObject:url];
  219. SD_UNLOCK(self.failedURLsLock);
  220. }
  221. } else {
  222. if ((options & SDWebImageRetryFailed)) {
  223. SD_LOCK(self.failedURLsLock);
  224. [self.failedURLs removeObject:url];
  225. SD_UNLOCK(self.failedURLsLock);
  226. }
  227. [self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
  228. }
  229. if (finished) {
  230. [self safelyRemoveOperationFromRunning:operation];
  231. }
  232. }];
  233. } else if (cachedImage) {
  234. [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
  235. [self safelyRemoveOperationFromRunning:operation];
  236. } else {
  237. // Image not in cache and download disallowed by delegate
  238. [self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
  239. [self safelyRemoveOperationFromRunning:operation];
  240. }
  241. }
  242. // Store cache process
  243. - (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
  244. url:(nonnull NSURL *)url
  245. options:(SDWebImageOptions)options
  246. context:(SDWebImageContext *)context
  247. downloadedImage:(nullable UIImage *)downloadedImage
  248. downloadedData:(nullable NSData *)downloadedData
  249. finished:(BOOL)finished
  250. progress:(nullable SDImageLoaderProgressBlock)progressBlock
  251. completed:(nullable SDInternalCompletionBlock)completedBlock {
  252. SDImageCacheType storeCacheType = SDImageCacheTypeAll;
  253. if (context[SDWebImageContextStoreCacheType]) {
  254. storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
  255. }
  256. id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
  257. NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
  258. id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
  259. id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
  260. if (downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer) {
  261. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  262. @autoreleasepool {
  263. UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
  264. if (transformedImage && finished) {
  265. NSString *transformerKey = [transformer transformerKey];
  266. NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
  267. BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
  268. NSData *cacheData;
  269. // pass nil if the image was transformed, so we can recalculate the data from the image
  270. if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
  271. cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
  272. } else {
  273. cacheData = (imageWasTransformed ? nil : downloadedData);
  274. }
  275. [self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
  276. }
  277. [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
  278. }
  279. });
  280. } else {
  281. if (downloadedImage && finished) {
  282. if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
  283. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
  284. @autoreleasepool {
  285. NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
  286. [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:storeCacheType completion:nil];
  287. }
  288. });
  289. } else {
  290. [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:storeCacheType completion:nil];
  291. }
  292. }
  293. [self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
  294. }
  295. }
  296. #pragma mark - Helper
  297. - (void)safelyRemoveOperationFromRunning:(nullable SDWebImageCombinedOperation*)operation {
  298. if (!operation) {
  299. return;
  300. }
  301. SD_LOCK(self.runningOperationsLock);
  302. [self.runningOperations removeObject:operation];
  303. SD_UNLOCK(self.runningOperationsLock);
  304. }
  305. - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
  306. completion:(nullable SDInternalCompletionBlock)completionBlock
  307. error:(nullable NSError *)error
  308. url:(nullable NSURL *)url {
  309. [self callCompletionBlockForOperation:operation completion:completionBlock image:nil data:nil error:error cacheType:SDImageCacheTypeNone finished:YES url:url];
  310. }
  311. - (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
  312. completion:(nullable SDInternalCompletionBlock)completionBlock
  313. image:(nullable UIImage *)image
  314. data:(nullable NSData *)data
  315. error:(nullable NSError *)error
  316. cacheType:(SDImageCacheType)cacheType
  317. finished:(BOOL)finished
  318. url:(nullable NSURL *)url {
  319. dispatch_main_async_safe(^{
  320. if (operation && !operation.isCancelled && completionBlock) {
  321. completionBlock(image, data, error, cacheType, finished, url);
  322. }
  323. });
  324. }
  325. - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
  326. error:(nonnull NSError *)error {
  327. // Check whether we should block failed url
  328. BOOL shouldBlockFailedURL;
  329. if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
  330. shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
  331. } else {
  332. shouldBlockFailedURL = [self.imageLoader shouldBlockFailedURLWithURL:url error:error];
  333. }
  334. return shouldBlockFailedURL;
  335. }
  336. - (SDWebImageContext *)processedContextWithContext:(SDWebImageContext *)context {
  337. SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
  338. // Image Transformer from manager
  339. if (!context[SDWebImageContextImageTransformer]) {
  340. id<SDImageTransformer> transformer = self.transformer;
  341. [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
  342. }
  343. // Cache key filter from manager
  344. if (!context[SDWebImageContextCacheKeyFilter]) {
  345. id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
  346. [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
  347. }
  348. // Cache serializer from manager
  349. if (!context[SDWebImageContextCacheSerializer]) {
  350. id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
  351. [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
  352. }
  353. if (mutableContext.count == 0) {
  354. return context;
  355. } else {
  356. [mutableContext addEntriesFromDictionary:context];
  357. return [mutableContext copy];
  358. }
  359. }
  360. @end
  361. @implementation SDWebImageCombinedOperation
  362. - (void)cancel {
  363. @synchronized(self) {
  364. if (self.isCancelled) {
  365. return;
  366. }
  367. self.cancelled = YES;
  368. if (self.cacheOperation) {
  369. [self.cacheOperation cancel];
  370. self.cacheOperation = nil;
  371. }
  372. if (self.loaderOperation) {
  373. [self.loaderOperation cancel];
  374. self.loaderOperation = nil;
  375. }
  376. [self.manager safelyRemoveOperationFromRunning:self];
  377. }
  378. }
  379. @end