SDWebImagePrefetcher.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  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 "SDWebImagePrefetcher.h"
  9. #import "SDAsyncBlockOperation.h"
  10. #import "SDCallbackQueue.h"
  11. #import "SDInternalMacros.h"
  12. #import <stdatomic.h>
  13. @interface SDCallbackQueue ()
  14. @property (nonatomic, strong, nonnull) dispatch_queue_t queue;
  15. @end
  16. @interface SDWebImagePrefetchToken () {
  17. @public
  18. // Though current implementation, `SDWebImageManager` completion block is always on main queue. But however, there is no guarantee in docs. And we may introduce config to specify custom queue in the future.
  19. // These value are just used as incrementing counter, keep thread-safe using memory_order_relaxed for performance.
  20. atomic_ulong _skippedCount;
  21. atomic_ulong _finishedCount;
  22. atomic_flag _isAllFinished;
  23. unsigned long _totalCount;
  24. // Used to ensure NSPointerArray thread safe
  25. SD_LOCK_DECLARE(_prefetchOperationsLock);
  26. SD_LOCK_DECLARE(_loadOperationsLock);
  27. }
  28. @property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
  29. @property (nonatomic, strong) NSPointerArray *loadOperations;
  30. @property (nonatomic, strong) NSPointerArray *prefetchOperations;
  31. @property (nonatomic, weak) SDWebImagePrefetcher *prefetcher;
  32. @property (nonatomic, assign) SDWebImageOptions options;
  33. @property (nonatomic, copy, nullable) SDWebImageContext *context;
  34. @property (nonatomic, copy, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
  35. @property (nonatomic, copy, nullable) SDWebImagePrefetcherProgressBlock progressBlock;
  36. @end
  37. @interface SDWebImagePrefetcher ()
  38. @property (strong, nonatomic, nonnull) SDWebImageManager *manager;
  39. @property (strong, atomic, nonnull) NSMutableSet<SDWebImagePrefetchToken *> *runningTokens;
  40. @property (strong, nonatomic, nonnull) NSOperationQueue *prefetchQueue;
  41. @property (strong, nonatomic, nullable) SDCallbackQueue *callbackQueue;
  42. @end
  43. @implementation SDWebImagePrefetcher
  44. + (nonnull instancetype)sharedImagePrefetcher {
  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 initWithImageManager:[SDWebImageManager new]];
  54. }
  55. - (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager {
  56. if ((self = [super init])) {
  57. _manager = manager;
  58. _runningTokens = [NSMutableSet set];
  59. _options = SDWebImageLowPriority;
  60. _prefetchQueue = [NSOperationQueue new];
  61. self.maxConcurrentPrefetchCount = 3;
  62. }
  63. return self;
  64. }
  65. - (void)setMaxConcurrentPrefetchCount:(NSUInteger)maxConcurrentPrefetchCount {
  66. self.prefetchQueue.maxConcurrentOperationCount = maxConcurrentPrefetchCount;
  67. }
  68. - (NSUInteger)maxConcurrentPrefetchCount {
  69. return self.prefetchQueue.maxConcurrentOperationCount;
  70. }
  71. - (void)setDelegateQueue:(dispatch_queue_t)delegateQueue {
  72. // Deprecate and translate to SDCallbackQueue
  73. _callbackQueue = [[SDCallbackQueue alloc] initWithDispatchQueue:delegateQueue];
  74. _callbackQueue.policy = SDCallbackPolicyDispatch;
  75. }
  76. - (dispatch_queue_t)delegateQueue {
  77. // Deprecate and translate to SDCallbackQueue
  78. return (_callbackQueue ?: SDCallbackQueue.mainQueue).queue;
  79. }
  80. #pragma mark - Prefetch
  81. - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls {
  82. return [self prefetchURLs:urls progress:nil completed:nil];
  83. }
  84. - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
  85. progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
  86. completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
  87. return [self prefetchURLs:urls options:self.options context:self.context progress:progressBlock completed:completionBlock];
  88. }
  89. - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
  90. options:(SDWebImageOptions)options
  91. context:(nullable SDWebImageContext *)context
  92. progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
  93. completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
  94. if (!urls || urls.count == 0) {
  95. if (completionBlock) {
  96. completionBlock(0, 0);
  97. }
  98. return nil;
  99. }
  100. SDWebImagePrefetchToken *token = [SDWebImagePrefetchToken new];
  101. token.prefetcher = self;
  102. token.urls = urls;
  103. token.options = options;
  104. token.context = context;
  105. token->_skippedCount = 0;
  106. token->_finishedCount = 0;
  107. token->_totalCount = token.urls.count;
  108. atomic_flag_clear(&(token->_isAllFinished));
  109. token.loadOperations = [NSPointerArray weakObjectsPointerArray];
  110. token.prefetchOperations = [NSPointerArray weakObjectsPointerArray];
  111. token.progressBlock = progressBlock;
  112. token.completionBlock = completionBlock;
  113. [self addRunningToken:token];
  114. [self startPrefetchWithToken:token];
  115. return token;
  116. }
  117. - (void)startPrefetchWithToken:(SDWebImagePrefetchToken * _Nonnull)token {
  118. for (NSURL *url in token.urls) {
  119. @weakify(self);
  120. SDAsyncBlockOperation *prefetchOperation = [SDAsyncBlockOperation blockOperationWithBlock:^(SDAsyncBlockOperation * _Nonnull asyncOperation) {
  121. @strongify(self);
  122. if (!self || asyncOperation.isCancelled) {
  123. return;
  124. }
  125. id<SDWebImageOperation> operation = [self.manager loadImageWithURL:url options:token.options context:token.context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
  126. @strongify(self);
  127. if (!self) {
  128. return;
  129. }
  130. if (!finished) {
  131. return;
  132. }
  133. atomic_fetch_add_explicit(&(token->_finishedCount), 1, memory_order_relaxed);
  134. if (error) {
  135. // Add last failed
  136. atomic_fetch_add_explicit(&(token->_skippedCount), 1, memory_order_relaxed);
  137. }
  138. // Current operation finished
  139. [self callProgressBlockForToken:token imageURL:imageURL];
  140. if (atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed) == token->_totalCount) {
  141. // All finished
  142. if (!atomic_flag_test_and_set_explicit(&(token->_isAllFinished), memory_order_relaxed)) {
  143. [self callCompletionBlockForToken:token];
  144. [self removeRunningToken:token];
  145. }
  146. }
  147. [asyncOperation complete];
  148. }];
  149. NSAssert(operation != nil, @"Operation should not be nil, [SDWebImageManager loadImageWithURL:options:context:progress:completed:] break prefetch logic");
  150. SD_LOCK(token->_loadOperationsLock);
  151. [token.loadOperations addPointer:(__bridge void *)operation];
  152. SD_UNLOCK(token->_loadOperationsLock);
  153. }];
  154. SD_LOCK(token->_prefetchOperationsLock);
  155. [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation];
  156. SD_UNLOCK(token->_prefetchOperationsLock);
  157. [self.prefetchQueue addOperation:prefetchOperation];
  158. }
  159. }
  160. #pragma mark - Cancel
  161. - (void)cancelPrefetching {
  162. @synchronized(self.runningTokens) {
  163. NSSet<SDWebImagePrefetchToken *> *copiedTokens = [self.runningTokens copy];
  164. [copiedTokens makeObjectsPerformSelector:@selector(cancel)];
  165. [self.runningTokens removeAllObjects];
  166. }
  167. }
  168. - (void)callProgressBlockForToken:(SDWebImagePrefetchToken *)token imageURL:(NSURL *)url {
  169. if (!token) {
  170. return;
  171. }
  172. BOOL shouldCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)];
  173. NSUInteger tokenFinishedCount = [self tokenFinishedCount];
  174. NSUInteger tokenTotalCount = [self tokenTotalCount];
  175. NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
  176. NSUInteger totalCount = token->_totalCount;
  177. SDCallbackQueue *queue = token.context[SDWebImageContextCallbackQueue];
  178. if (!queue) {
  179. queue = self.callbackQueue;
  180. }
  181. [(queue ?: SDCallbackQueue.mainQueue) async:^{
  182. if (shouldCallDelegate) {
  183. [self.delegate imagePrefetcher:self didPrefetchURL:url finishedCount:tokenFinishedCount totalCount:tokenTotalCount];
  184. }
  185. if (token.progressBlock) {
  186. token.progressBlock(finishedCount, totalCount);
  187. }
  188. }];
  189. }
  190. - (void)callCompletionBlockForToken:(SDWebImagePrefetchToken *)token {
  191. if (!token) {
  192. return;
  193. }
  194. BOOL shoulCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)] && ([self countOfRunningTokens] == 1); // last one
  195. NSUInteger tokenTotalCount = [self tokenTotalCount];
  196. NSUInteger tokenSkippedCount = [self tokenSkippedCount];
  197. NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
  198. NSUInteger skippedCount = atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed);
  199. SDCallbackQueue *queue = token.context[SDWebImageContextCallbackQueue];
  200. if (!queue) {
  201. queue = self.callbackQueue;
  202. }
  203. [(queue ?: SDCallbackQueue.mainQueue) async:^{
  204. if (shoulCallDelegate) {
  205. [self.delegate imagePrefetcher:self didFinishWithTotalCount:tokenTotalCount skippedCount:tokenSkippedCount];
  206. }
  207. if (token.completionBlock) {
  208. token.completionBlock(finishedCount, skippedCount);
  209. }
  210. }];
  211. }
  212. #pragma mark - Helper
  213. - (NSUInteger)tokenTotalCount {
  214. NSUInteger tokenTotalCount = 0;
  215. @synchronized (self.runningTokens) {
  216. for (SDWebImagePrefetchToken *token in self.runningTokens) {
  217. tokenTotalCount += token->_totalCount;
  218. }
  219. }
  220. return tokenTotalCount;
  221. }
  222. - (NSUInteger)tokenSkippedCount {
  223. NSUInteger tokenSkippedCount = 0;
  224. @synchronized (self.runningTokens) {
  225. for (SDWebImagePrefetchToken *token in self.runningTokens) {
  226. tokenSkippedCount += atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed);
  227. }
  228. }
  229. return tokenSkippedCount;
  230. }
  231. - (NSUInteger)tokenFinishedCount {
  232. NSUInteger tokenFinishedCount = 0;
  233. @synchronized (self.runningTokens) {
  234. for (SDWebImagePrefetchToken *token in self.runningTokens) {
  235. tokenFinishedCount += atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
  236. }
  237. }
  238. return tokenFinishedCount;
  239. }
  240. - (void)addRunningToken:(SDWebImagePrefetchToken *)token {
  241. if (!token) {
  242. return;
  243. }
  244. @synchronized (self.runningTokens) {
  245. [self.runningTokens addObject:token];
  246. }
  247. }
  248. - (void)removeRunningToken:(SDWebImagePrefetchToken *)token {
  249. if (!token) {
  250. return;
  251. }
  252. @synchronized (self.runningTokens) {
  253. [self.runningTokens removeObject:token];
  254. }
  255. }
  256. - (NSUInteger)countOfRunningTokens {
  257. NSUInteger count = 0;
  258. @synchronized (self.runningTokens) {
  259. count = self.runningTokens.count;
  260. }
  261. return count;
  262. }
  263. @end
  264. @implementation SDWebImagePrefetchToken
  265. - (instancetype)init {
  266. self = [super init];
  267. if (self) {
  268. SD_LOCK_INIT(_prefetchOperationsLock);
  269. SD_LOCK_INIT(_loadOperationsLock);
  270. }
  271. return self;
  272. }
  273. - (void)cancel {
  274. SD_LOCK(_prefetchOperationsLock);
  275. [self.prefetchOperations compact];
  276. for (id operation in self.prefetchOperations) {
  277. id<SDWebImageOperation> strongOperation = operation;
  278. if (strongOperation) {
  279. [strongOperation cancel];
  280. }
  281. }
  282. self.prefetchOperations.count = 0;
  283. SD_UNLOCK(_prefetchOperationsLock);
  284. SD_LOCK(_loadOperationsLock);
  285. [self.loadOperations compact];
  286. for (id operation in self.loadOperations) {
  287. id<SDWebImageOperation> strongOperation = operation;
  288. if (strongOperation) {
  289. [strongOperation cancel];
  290. }
  291. }
  292. self.loadOperations.count = 0;
  293. SD_UNLOCK(_loadOperationsLock);
  294. self.completionBlock = nil;
  295. self.progressBlock = nil;
  296. [self.prefetcher removeRunningToken:self];
  297. }
  298. @end