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); } }