From 67e0df75c6be7a71afc6f9d4aa60a2a930cfb900 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Thu, 2 Aug 2018 12:08:18 +0800 Subject: [PATCH] Add one feature, to allow user to limit the progress interval and reduce the frequency of progress callback Also fix that progressive decoding should stop when all image data is downloaded --- SDWebImage/SDWebImageDownloader.m | 16 ++++++--- SDWebImage/SDWebImageDownloaderConfig.h | 9 +++++ SDWebImage/SDWebImageDownloaderConfig.m | 1 + SDWebImage/SDWebImageDownloaderOperation.h | 16 +++++++-- SDWebImage/SDWebImageDownloaderOperation.m | 38 +++++++++++++++++----- 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 7dd3c7aa..73449530 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -200,10 +200,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; } NSOperation *operation = [[operationClass alloc] initWithRequest:request inSession:sself.session options:options context:context]; - if (sself.config.urlCredential) { - operation.credential = sself.config.urlCredential; - } else if (sself.config.username && sself.config.password) { - operation.credential = [NSURLCredential credentialWithUser:sself.config.username password:sself.config.password persistence:NSURLCredentialPersistenceForSession]; + if ([operation respondsToSelector:@selector(setCredential:)]) { + if (sself.config.urlCredential) { + operation.credential = sself.config.urlCredential; + } else if (sself.config.username && sself.config.password) { + operation.credential = [NSURLCredential credentialWithUser:sself.config.username password:sself.config.password persistence:NSURLCredentialPersistenceForSession]; + } + } + + if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) { + NSTimeInterval minimumProgressInterval = sself.config.minimumProgressInterval; + minimumProgressInterval = MIN(MAX(minimumProgressInterval, 0), 1); + operation.minimumProgressInterval = minimumProgressInterval; } if (options & SDWebImageDownloaderHighPriority) { diff --git a/SDWebImage/SDWebImageDownloaderConfig.h b/SDWebImage/SDWebImageDownloaderConfig.h index 1b5d833e..00f15496 100644 --- a/SDWebImage/SDWebImageDownloaderConfig.h +++ b/SDWebImage/SDWebImageDownloaderConfig.h @@ -41,6 +41,15 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { */ @property (nonatomic, assign) NSTimeInterval downloadTimeout; +/** + * The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value. + * The value should be 0.0-1.0. + * @note If you're using progressive decoding feature, this will also effect the image refresh rate. + * @note This value may enhance the performance if you don't want progress callback too frequently. + * Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately. + */ +@property (nonatomic, assign) NSTimeInterval minimumProgressInterval; + /** * The custom session configuration in use by NSURLSession. If you don't provide one, we will use `defaultSessionConfiguration` instead. * Defatuls to nil. diff --git a/SDWebImage/SDWebImageDownloaderConfig.m b/SDWebImage/SDWebImageDownloaderConfig.m index 79e15c9d..1fc93308 100644 --- a/SDWebImage/SDWebImageDownloaderConfig.m +++ b/SDWebImage/SDWebImageDownloaderConfig.m @@ -34,6 +34,7 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig; SDWebImageDownloaderConfig *config = [[[self class] allocWithZone:zone] init]; config.maxConcurrentDownloads = self.maxConcurrentDownloads; config.downloadTimeout = self.downloadTimeout; + config.minimumProgressInterval = self.minimumProgressInterval; config.sessionConfiguration = [self.sessionConfiguration copyWithZone:zone]; config.operationClass = self.operationClass; config.executionOrder = self.executionOrder; diff --git a/SDWebImage/SDWebImageDownloaderOperation.h b/SDWebImage/SDWebImageDownloaderOperation.h index c5edfb1e..ff5c6129 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.h +++ b/SDWebImage/SDWebImageDownloaderOperation.h @@ -36,9 +36,6 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; -- (nullable NSURLCredential *)credential; -- (void)setCredential:(nullable NSURLCredential *)value; - - (BOOL)cancel:(nullable id)token; - (nullable NSURLRequest *)request; @@ -46,6 +43,10 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification @optional - (nullable NSURLSessionTask *)dataTask; +- (nullable NSURLCredential *)credential; +- (void)setCredential:(nullable NSURLCredential *)credential; +- (NSTimeInterval)minimumProgressInterval; +- (void)setMinimumProgressInterval:(NSTimeInterval)minimumProgressInterval; @end @@ -74,6 +75,15 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification */ @property (nonatomic, strong, nullable) NSURLCredential *credential; +/** + * The minimum interval about progress percent during network downloading. Which means the next progress callback and current progress callback's progress percent difference should be larger or equal to this value. + * The value should be 0.0-1.0. + * @note If you're using progressive decoding feature, this will also effect the image refresh rate. + * @note This value may enhance the performance if you don't want progress callback too frequently. + * Defaults to 0, which means each time we receive the new data from URLSession, we callback the progressBlock immediately. + */ +@property (assign, nonatomic) NSTimeInterval minimumProgressInterval; + /** * The options for the receiver. */ diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index 5232400b..a6c13fa7 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -37,9 +37,11 @@ typedef NSMutableDictionary SDCallbacksDictionary; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (strong, nonatomic, nullable) NSMutableData *imageData; @property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse` -@property (assign, nonatomic, readwrite) NSUInteger expectedSize; +@property (assign, nonatomic) NSUInteger expectedSize; // may be 0 +@property (assign, nonatomic) NSUInteger receivedSize; @property (strong, nonatomic, nullable, readwrite) NSURLResponse *response; @property (strong, nonatomic, nullable) NSError *responseError; +@property (assign, nonatomic) double previousProgress; // previous progress percent // This is weak because it is injected by whoever manages this session. If this gets nil-ed out, we won't be able to run // the task associated with this operation @@ -331,17 +333,37 @@ didReceiveResponse:(NSURLResponse *)response self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize]; } [self.imageData appendData:data]; + + self.receivedSize = self.imageData.length; + if (self.expectedSize == 0) { + // Unknown expectedSize, immediately call progressBlock and return + for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { + progressBlock(self.receivedSize, self.expectedSize, self.request.URL); + } + return; + } + + // Get the finish status + BOOL finished = (self.receivedSize >= self.expectedSize); + // Get the current progress + double currentProgress = (double)self.receivedSize / (double)self.expectedSize; + double previousProgress = self.previousProgress; + // Check if we need callback progress + if (currentProgress - previousProgress < self.minimumProgressInterval) { + return; + } + self.previousProgress = currentProgress; - if ((self.options & SDWebImageDownloaderProgressiveLoad) && self.expectedSize > 0) { + if (self.options & SDWebImageDownloaderProgressiveLoad) { // Get the image data NSData *imageData = [self.imageData copy]; - // Get the total bytes downloaded - const NSUInteger totalSize = imageData.length; - // Get the finish status - BOOL finished = (totalSize >= self.expectedSize); // progressive decode the image in coder queue dispatch_async(self.coderQueue, ^{ + // If all the data has already been downloaded, earily return to avoid further decoding + if (self.receivedSize >= self.expectedSize) { + return; + } UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); if (image) { // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding. @@ -350,9 +372,9 @@ didReceiveResponse:(NSURLResponse *)response } }); } - + for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { - progressBlock(self.imageData.length, self.expectedSize, self.request.URL); + progressBlock(self.receivedSize, self.expectedSize, self.request.URL); } }