SDWebImagePrefetcher.m 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. @interface SDWebImagePrefetcher ()
  10. @property (strong, nonatomic, nonnull) SDWebImageManager *manager;
  11. @property (strong, atomic, nullable) NSArray<NSURL *> *prefetchURLs; // may be accessed from different queue
  12. @property (assign, nonatomic) NSUInteger requestedCount;
  13. @property (assign, nonatomic) NSUInteger skippedCount;
  14. @property (assign, nonatomic) NSUInteger finishedCount;
  15. @property (assign, nonatomic) NSTimeInterval startedTime;
  16. @property (copy, nonatomic, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
  17. @property (copy, nonatomic, nullable) SDWebImagePrefetcherProgressBlock progressBlock;
  18. @end
  19. @implementation SDWebImagePrefetcher
  20. + (nonnull instancetype)sharedImagePrefetcher {
  21. static dispatch_once_t once;
  22. static id instance;
  23. dispatch_once(&once, ^{
  24. instance = [self new];
  25. });
  26. return instance;
  27. }
  28. - (nonnull instancetype)init {
  29. return [self initWithImageManager:[SDWebImageManager new]];
  30. }
  31. - (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager {
  32. if ((self = [super init])) {
  33. _manager = manager;
  34. _options = SDWebImageLowPriority;
  35. _prefetcherQueue = dispatch_get_main_queue();
  36. self.maxConcurrentDownloads = 3;
  37. }
  38. return self;
  39. }
  40. - (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads {
  41. self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads;
  42. }
  43. - (NSUInteger)maxConcurrentDownloads {
  44. return self.manager.imageDownloader.maxConcurrentDownloads;
  45. }
  46. - (void)startPrefetchingAtIndex:(NSUInteger)index {
  47. NSURL *currentURL;
  48. @synchronized(self) {
  49. if (index >= self.prefetchURLs.count) return;
  50. currentURL = self.prefetchURLs[index];
  51. self.requestedCount++;
  52. }
  53. [self.manager loadImageWithURL:currentURL options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  54. if (!finished) return;
  55. self.finishedCount++;
  56. if (self.progressBlock) {
  57. self.progressBlock(self.finishedCount,(self.prefetchURLs).count);
  58. }
  59. if (!image) {
  60. // Add last failed
  61. self.skippedCount++;
  62. }
  63. if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) {
  64. [self.delegate imagePrefetcher:self
  65. didPrefetchURL:currentURL
  66. finishedCount:self.finishedCount
  67. totalCount:self.prefetchURLs.count
  68. ];
  69. }
  70. if (self.prefetchURLs.count > self.requestedCount) {
  71. dispatch_async(self.prefetcherQueue, ^{
  72. // we need dispatch to avoid function recursion call. This can prevent stack overflow even for huge urls list
  73. [self startPrefetchingAtIndex:self.requestedCount];
  74. });
  75. } else if (self.finishedCount == self.requestedCount) {
  76. [self reportStatus];
  77. if (self.completionBlock) {
  78. self.completionBlock(self.finishedCount, self.skippedCount);
  79. self.completionBlock = nil;
  80. }
  81. self.progressBlock = nil;
  82. }
  83. }];
  84. }
  85. - (void)reportStatus {
  86. NSUInteger total = (self.prefetchURLs).count;
  87. if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) {
  88. [self.delegate imagePrefetcher:self
  89. didFinishWithTotalCount:(total - self.skippedCount)
  90. skippedCount:self.skippedCount
  91. ];
  92. }
  93. }
  94. - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls {
  95. [self prefetchURLs:urls progress:nil completed:nil];
  96. }
  97. - (void)prefetchURLs:(nullable NSArray<NSURL *> *)urls
  98. progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
  99. completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
  100. [self cancelPrefetching]; // Prevent duplicate prefetch request
  101. self.startedTime = CFAbsoluteTimeGetCurrent();
  102. self.prefetchURLs = urls;
  103. self.completionBlock = completionBlock;
  104. self.progressBlock = progressBlock;
  105. if (urls.count == 0) {
  106. if (completionBlock) {
  107. completionBlock(0,0);
  108. }
  109. } else {
  110. // Starts prefetching from the very first image on the list with the max allowed concurrency
  111. NSUInteger listCount = self.prefetchURLs.count;
  112. for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) {
  113. [self startPrefetchingAtIndex:i];
  114. }
  115. }
  116. }
  117. - (void)cancelPrefetching {
  118. @synchronized(self) {
  119. self.prefetchURLs = nil;
  120. self.skippedCount = 0;
  121. self.requestedCount = 0;
  122. self.finishedCount = 0;
  123. }
  124. [self.manager cancelAll];
  125. }
  126. @end