123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDWebImagePrefetcher.h"
- #import "SDAsyncBlockOperation.h"
- #import "SDInternalMacros.h"
- #import <stdatomic.h>
- @interface SDWebImagePrefetchToken () {
- @public
- // 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.
- // These value are just used as incrementing counter, keep thread-safe using memory_order_relaxed for performance.
- atomic_ulong _skippedCount;
- atomic_ulong _finishedCount;
- atomic_flag _isAllFinished;
-
- unsigned long _totalCount;
- }
- @property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
- @property (nonatomic, strong) NSPointerArray *loadOperations;
- @property (nonatomic, strong) NSPointerArray *prefetchOperations;
- @property (nonatomic, weak) SDWebImagePrefetcher *prefetcher;
- @property (nonatomic, copy, nullable) SDWebImagePrefetcherCompletionBlock completionBlock;
- @property (nonatomic, copy, nullable) SDWebImagePrefetcherProgressBlock progressBlock;
- @end
- @interface SDWebImagePrefetcher ()
- @property (strong, nonatomic, nonnull) SDWebImageManager *manager;
- @property (strong, atomic, nonnull) NSMutableSet<SDWebImagePrefetchToken *> *runningTokens;
- @property (strong, nonatomic, nonnull) NSOperationQueue *prefetchQueue;
- @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;
- _runningTokens = [NSMutableSet set];
- _options = SDWebImageLowPriority;
- _delegateQueue = dispatch_get_main_queue();
- _prefetchQueue = [NSOperationQueue new];
- self.maxConcurrentPrefetchCount = 3;
- }
- return self;
- }
- - (void)setMaxConcurrentPrefetchCount:(NSUInteger)maxConcurrentPrefetchCount {
- self.prefetchQueue.maxConcurrentOperationCount = maxConcurrentPrefetchCount;
- }
- - (NSUInteger)maxConcurrentPrefetchCount {
- return self.prefetchQueue.maxConcurrentOperationCount;
- }
- #pragma mark - Prefetch
- - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls {
- return [self prefetchURLs:urls progress:nil completed:nil];
- }
- - (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls
- progress:(nullable SDWebImagePrefetcherProgressBlock)progressBlock
- completed:(nullable SDWebImagePrefetcherCompletionBlock)completionBlock {
- if (!urls || urls.count == 0) {
- if (completionBlock) {
- completionBlock(0, 0);
- }
- return nil;
- }
- SDWebImagePrefetchToken *token = [SDWebImagePrefetchToken new];
- token.prefetcher = self;
- token.urls = urls;
- token->_skippedCount = 0;
- token->_finishedCount = 0;
- token->_totalCount = token.urls.count;
- atomic_flag_clear(&(token->_isAllFinished));
- token.loadOperations = [NSPointerArray weakObjectsPointerArray];
- token.prefetchOperations = [NSPointerArray weakObjectsPointerArray];
- token.progressBlock = progressBlock;
- token.completionBlock = completionBlock;
- [self addRunningToken:token];
- [self startPrefetchWithToken:token];
-
- return token;
- }
- - (void)startPrefetchWithToken:(SDWebImagePrefetchToken * _Nonnull)token {
- NSPointerArray *operations = token.loadOperations;
- for (NSURL *url in token.urls) {
- @weakify(self);
- SDAsyncBlockOperation *prefetchOperation = [SDAsyncBlockOperation blockOperationWithBlock:^(SDAsyncBlockOperation * _Nonnull asyncOperation) {
- @strongify(self);
- if (!self || asyncOperation.isCancelled) {
- return;
- }
- id<SDWebImageOperation> operation = [self.manager loadImageWithURL:url options:self.options context:self.context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
- @strongify(self);
- if (!self) {
- return;
- }
- if (!finished) {
- return;
- }
- atomic_fetch_add_explicit(&(token->_finishedCount), 1, memory_order_relaxed);
- if (error) {
- // Add last failed
- atomic_fetch_add_explicit(&(token->_skippedCount), 1, memory_order_relaxed);
- }
-
- // Current operation finished
- [self callProgressBlockForToken:token imageURL:imageURL];
-
- if (atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed) == token->_totalCount) {
- // All finished
- if (!atomic_flag_test_and_set_explicit(&(token->_isAllFinished), memory_order_relaxed)) {
- [self callCompletionBlockForToken:token];
- [self removeRunningToken:token];
- }
- }
- [asyncOperation complete];
- }];
- NSAssert(operation != nil, @"Operation should not be nil, [SDWebImageManager loadImageWithURL:options:context:progress:completed:] break prefetch logic");
- @synchronized (token) {
- [operations addPointer:(__bridge void *)operation];
- }
- }];
- @synchronized (token) {
- [token.prefetchOperations addPointer:(__bridge void *)prefetchOperation];
- }
- [self.prefetchQueue addOperation:prefetchOperation];
- }
- }
- #pragma mark - Cancel
- - (void)cancelPrefetching {
- @synchronized(self.runningTokens) {
- NSSet<SDWebImagePrefetchToken *> *copiedTokens = [self.runningTokens copy];
- [copiedTokens makeObjectsPerformSelector:@selector(cancel)];
- [self.runningTokens removeAllObjects];
- }
- }
- - (void)callProgressBlockForToken:(SDWebImagePrefetchToken *)token imageURL:(NSURL *)url {
- if (!token) {
- return;
- }
- BOOL shouldCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didPrefetchURL:finishedCount:totalCount:)];
- NSUInteger tokenFinishedCount = [self tokenFinishedCount];
- NSUInteger tokenTotalCount = [self tokenTotalCount];
- NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
- NSUInteger totalCount = token->_totalCount;
- dispatch_async(self.delegateQueue, ^{
- if (shouldCallDelegate) {
- [self.delegate imagePrefetcher:self didPrefetchURL:url finishedCount:tokenFinishedCount totalCount:tokenTotalCount];
- }
- if (token.progressBlock) {
- token.progressBlock(finishedCount, totalCount);
- }
- });
- }
- - (void)callCompletionBlockForToken:(SDWebImagePrefetchToken *)token {
- if (!token) {
- return;
- }
- BOOL shoulCallDelegate = [self.delegate respondsToSelector:@selector(imagePrefetcher:didFinishWithTotalCount:skippedCount:)] && ([self countOfRunningTokens] == 1); // last one
- NSUInteger tokenTotalCount = [self tokenTotalCount];
- NSUInteger tokenSkippedCount = [self tokenSkippedCount];
- NSUInteger finishedCount = atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
- NSUInteger skippedCount = atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed);
- dispatch_async(self.delegateQueue, ^{
- if (shoulCallDelegate) {
- [self.delegate imagePrefetcher:self didFinishWithTotalCount:tokenTotalCount skippedCount:tokenSkippedCount];
- }
- if (token.completionBlock) {
- token.completionBlock(finishedCount, skippedCount);
- }
- });
- }
- #pragma mark - Helper
- - (NSUInteger)tokenTotalCount {
- NSUInteger tokenTotalCount = 0;
- @synchronized (self.runningTokens) {
- for (SDWebImagePrefetchToken *token in self.runningTokens) {
- tokenTotalCount += token->_totalCount;
- }
- }
- return tokenTotalCount;
- }
- - (NSUInteger)tokenSkippedCount {
- NSUInteger tokenSkippedCount = 0;
- @synchronized (self.runningTokens) {
- for (SDWebImagePrefetchToken *token in self.runningTokens) {
- tokenSkippedCount += atomic_load_explicit(&(token->_skippedCount), memory_order_relaxed);
- }
- }
- return tokenSkippedCount;
- }
- - (NSUInteger)tokenFinishedCount {
- NSUInteger tokenFinishedCount = 0;
- @synchronized (self.runningTokens) {
- for (SDWebImagePrefetchToken *token in self.runningTokens) {
- tokenFinishedCount += atomic_load_explicit(&(token->_finishedCount), memory_order_relaxed);
- }
- }
- return tokenFinishedCount;
- }
- - (void)addRunningToken:(SDWebImagePrefetchToken *)token {
- if (!token) {
- return;
- }
- @synchronized (self.runningTokens) {
- [self.runningTokens addObject:token];
- }
- }
- - (void)removeRunningToken:(SDWebImagePrefetchToken *)token {
- if (!token) {
- return;
- }
- @synchronized (self.runningTokens) {
- [self.runningTokens removeObject:token];
- }
- }
- - (NSUInteger)countOfRunningTokens {
- NSUInteger count = 0;
- @synchronized (self.runningTokens) {
- count = self.runningTokens.count;
- }
- return count;
- }
- @end
- @implementation SDWebImagePrefetchToken
- - (void)cancel {
- @synchronized (self) {
- [self.prefetchOperations compact];
- for (id operation in self.prefetchOperations) {
- if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
- [operation cancel];
- }
- }
- self.prefetchOperations.count = 0;
-
- [self.loadOperations compact];
- for (id operation in self.loadOperations) {
- if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {
- [operation cancel];
- }
- }
- self.loadOperations.count = 0;
- }
- self.completionBlock = nil;
- self.progressBlock = nil;
- [self.prefetcher removeRunningToken:self];
- }
- @end
|