Merge pull request #2415 from dreampiggy/feature_minimum_progress_interval

Feature minimum progress interval
This commit is contained in:
Bogdan Poplauschi 2018-08-04 19:11:04 +03:00
commit 1d4823eb48
7 changed files with 93 additions and 16 deletions

View File

@ -250,10 +250,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];
if (self.config.urlCredential) {
operation.credential = self.config.urlCredential;
} else if (self.config.username && self.config.password) {
operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
if ([operation respondsToSelector:@selector(setCredential:)]) {
if (self.config.urlCredential) {
operation.credential = self.config.urlCredential;
} else if (self.config.username && self.config.password) {
operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
}
}
if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
NSTimeInterval minimumProgressInterval = self.config.minimumProgressInterval;
minimumProgressInterval = MIN(MAX(minimumProgressInterval, 0), 1);
operation.minimumProgressInterval = minimumProgressInterval;
}
if (options & SDWebImageDownloaderHighPriority) {

View File

@ -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.

View File

@ -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;

View File

@ -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.
*/

View File

@ -37,9 +37,11 @@ typedef NSMutableDictionary<NSString *, id> 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);
}
}

View File

@ -259,6 +259,34 @@
[self waitForExpectationsWithCommonTimeout];
}
- (void)test17ThatMinimumProgressIntervalWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Minimum progress interval"];
SDWebImageDownloaderConfig *config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
config.minimumProgressInterval = 0.51; // This will make the progress only callback once
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] initWithConfig:config];
NSURL *imageURL = [NSURL URLWithString:@"http://www.ioncannon.net/wp-content/uploads/2011/06/test2.webp"];
__block NSUInteger allProgressCount = 0; // All progress (including operation start / first HTTP response, etc)
__block NSUInteger validProgressCount = 0; // Only progress from `URLSession:dataTask:didReceiveData:`
[downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
allProgressCount++;
if (expectedSize <= 0 || receivedSize <= 0) {
// ignore the progress callback until we receive data
return;
}
validProgressCount++;
} completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
if (allProgressCount > 1 && validProgressCount == 1) {
[expectation fulfill];
} else {
XCTFail(@"Progress callback more than once");
}
}];
[self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
[downloader invalidateSessionAndCancel:YES];
}];
}
/**
* Per #883 - Fix multiple requests for same image and then canceling one
* Old SDWebImage (3.x) could not handle correctly multiple requests for the same image + cancel

View File

@ -15,7 +15,6 @@
*/
@interface SDWebImageTestDownloadOperation : NSOperation <SDWebImageDownloaderOperation>
@property (nonatomic, strong, nullable) NSURLCredential *credential;
@property (nonatomic, strong, nullable) NSURLRequest *request;
@property (nonatomic, strong, nullable) NSURLResponse *response;