/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import "SDWebImagePrefetcher.h" @interface SDWebImagePrefetcher () @property (strong, nonatomic, nonnull) SDWebImageManager *manager; @property (strong, atomic, nullable) NSArray *prefetchURLs; // may be accessed from different queue @property (assign, nonatomic) NSUInteger requestedCount; @property (assign, nonatomic) NSUInteger skippedCount; @property (assign, nonatomic) NSUInteger finishedCount; @property (assign, nonatomic) NSTimeInterval startedTime; @property (copy, nonatomic, nullable) SDWebImagePrefetcherCompletionBlock completionBlock; @property (copy, nonatomic, nullable) SDWebImagePrefetcherProgressBlock progressBlock; @end @implementation SDWebImagePrefetcher + (nonnull instancetype)sharedImagePrefetcher { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ instance = [self new]; }); return instance; } - (nonnull instancetype)init { return [self initWithImageManager:[SDWebImageManager new]]; } - (nonnull instancetype)initWithImageManager:(SDWebImageManager *)manager { if ((self = [super init])) { _manager = manager; _options = SDWebImageLowPriority; _prefetcherQueue = dispatch_get_main_queue(); self.maxConcurrentDownloads = 3; } return self; } - (void)setMaxConcurrentDownloads:(NSUInteger)maxConcurrentDownloads { self.manager.imageDownloader.maxConcurrentDownloads = maxConcurrentDownloads; } - (NSUInteger)maxConcurrentDownloads { return self.manager.imageDownloader.maxConcurrentDownloads; } - (void)startPrefetchingAtIndex:(NSUInteger)index { NSURL *currentURL; @synchronized(self) { if (index >= self.prefetchURLs.count) return; currentURL = self.prefetchURLs[index]; self.requestedCount++; } [self.manager loadImageWithURL:currentURL options:self.options progress:nil completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (!finished) return; self.finishedCount++; if (self.progressBlock) { self.progressBlock(self.finishedCount,(self.prefetchURLs).count); } if (!image) { // Add last failed self.skippedCount++; } if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)]) { [self.delegate imagePrefetcher:self didPrefetchURL:currentURL finishedCount:self.finishedCount totalCount:self.prefetchURLs.count ]; } if (self.prefetchURLs.count > self.requestedCount) { dispatch_async(self.prefetcherQueue, ^{ // we need dispatch to avoid function recursion call. This can prevent stack overflow even for huge urls list [self startPrefetchingAtIndex:self.requestedCount]; }); } else if (self.finishedCount == self.requestedCount) { [self reportStatus]; if (self.completionBlock) { self.completionBlock(self.finishedCount, self.skippedCount); self.completionBlock = nil; } self.progressBlock = nil; } }]; } - (void)reportStatus { NSUInteger total = (self.prefetchURLs).count; if ([self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)]) { [self.delegate imagePrefetcher:self didFinishWithTotalCount:(total - self.skippedCount) skippedCount:self.skippedCount ]; } } - (void)prefetchURLs:(nullable NSArray *)urls { [self prefetchURLs:urls progress:nil completed:nil]; } - (void)prefetchURLs:(nullable NSArray *)urls progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock { [self cancelPrefetching]; // Prevent duplicate prefetch request self.startedTime = CFAbsoluteTimeGetCurrent(); self.prefetchURLs = urls; self.completionBlock = completionBlock; self.progressBlock = progressBlock; if (urls.count == 0) { if (completionBlock) { completionBlock(0,0); } } else { // Starts prefetching from the very first image on the list with the max allowed concurrency NSUInteger listCount = self.prefetchURLs.count; for (NSUInteger i = 0; i < self.maxConcurrentDownloads && self.requestedCount < listCount; i++) { [self startPrefetchingAtIndex:i]; } } } - (void)cancelPrefetching { @synchronized(self) { self.prefetchURLs = nil; self.skippedCount = 0; self.requestedCount = 0; self.finishedCount = 0; } [self.manager cancelAll]; } @end