Add downloader request modifier to allow modify final HTTP request. Also open the API to allow advanced user to check cache & downloader operation.

This commit is contained in:
DreamPiggy 2018-03-24 00:15:27 +08:00
parent 01e75cfa7c
commit f01fe38fdd
11 changed files with 190 additions and 80 deletions

View File

@ -181,3 +181,8 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustom
This can be used to improve animated images rendering performance (especially memory usage on big animated images) with `SDAnimatedImageView` (Class).
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextAnimatedImageClass;
/**
A SDWebImageDownloaderRequestModifierBlock instance(dispatch_block_t) to modify the image download request. It's used for downloader to modify the original request from URL and options. If you provide one, it will ignore the `requestModifier` in downloader and use provided one instead. (SDWebImageDownloaderRequestModifierBlock)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextDownloadRequestModifier;

View File

@ -104,3 +104,4 @@ SDWebImageContextOption const SDWebImageContextSetImageGroup = @"setImageGroup";
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
SDWebImageContextOption const SDWebImageContextCustomTransformer = @"customTransformer";
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier";

View File

@ -81,10 +81,7 @@ typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteg
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);
typedef NSURLRequest * _Nullable (^SDWebImageDownloaderRequestModifierBlock)(NSURLRequest * _Nonnull request);
/**
* A token associated with each download. Can be used to cancel a download
@ -100,6 +97,10 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
The download's URL.
*/
@property (nonatomic, strong, nullable, readonly) NSURL *url;
/**
The doenload's response.
*/
@property (nonatomic, strong, nullable, readonly) NSURLResponse *response;
@end
@ -116,12 +117,11 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
@property (nonatomic, copy, readonly, nonnull) SDWebImageDownloaderConfig *config;
/**
* Set filter to pick headers for downloading image HTTP request.
*
* This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request.
* Set the request modifier to modify the original download request before image load.
* This block will be invoked for each downloading image request if provided. Return the original request means no modication. Return nil will cancel the download request.
* @note If you want to modify single request, consider using `SDWebImageContextDownloadRequestModifier` context option.
*/
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
@property (nonatomic, copy, nullable) SDWebImageDownloaderRequestModifierBlock requestModifier;
/**
* The configuration in use by the internal NSURLSession. If you want to provide a custom sessionConfiguration, use `SDWebImageDownloaderConfig.sessionConfiguration` and create a new downloader instance.
@ -217,13 +217,6 @@ typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterB
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
*
* @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
/**
* Cancels all download operations in the queue
*/

View File

@ -18,6 +18,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@interface SDWebImageDownloadToken ()
@property (nonatomic, strong, nullable, readwrite) NSURL *url;
@property (nonatomic, strong, nullable, readwrite) NSURLResponse *response;
@property (nonatomic, strong, nullable, readwrite) id downloadOperationCancelToken;
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperation> *downloadOperation;
@property (nonatomic, weak, nullable) SDWebImageDownloader *downloader;
@ -25,15 +26,13 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@end
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
@property (copy, atomic, nullable) NSDictionary<NSString *, NSString *> *HTTPHeaders; // Since modify this value is rare, use immutable object can enhance performance. But should mark as atomic to keep thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@ -91,12 +90,11 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
_HTTPHeaders = @{@"Accept": @"image/webp,image/*;q=0.8"};
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
_HTTPHeaders = @{@"Accept": @"image/*;q=0.8"};
#endif
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
@ -133,27 +131,20 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
LOCK(self.headersLock);
NSMutableDictionary *mutableHTTPHeaders = [self.HTTPHeaders mutableCopy];
if (value) {
self.HTTPHeaders[field] = value;
[mutableHTTPHeaders setObject:value forKey:field];
} else {
[self.HTTPHeaders removeObjectForKey:field];
[mutableHTTPHeaders removeObjectForKey:field];
}
UNLOCK(self.headersLock);
self.HTTPHeaders = [mutableHTTPHeaders copy];
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) {
return nil;
}
return [[self allHTTPHeaderFields] objectForKey:field];
}
- (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields {
LOCK(self.headersLock);
SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
UNLOCK(self.headersLock);
return allHTTPHeaderFields;
return [self.HTTPHeaders objectForKey:field];
}
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
@ -176,17 +167,28 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
mutableRequest.allHTTPHeaderFields = sself.HTTPHeaders;
SDWebImageDownloaderRequestModifierBlock requestModifier;
if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
} else {
requestModifier = self.requestModifier;
}
else {
request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
NSURLRequest *request;
if (requestModifier) {
NSURLRequest *modifiedRequest = requestModifier([mutableRequest copy]);
// If modified request is nil, early return
if (!modifiedRequest) {
return nil;
} else {
request = [modifiedRequest copy];
}
} else {
request = [mutableRequest copy];
}
Class operationClass = sself.config.operationClass;
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
@ -241,8 +243,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
createCallback:(NSOperation<SDWebImageDownloaderOperation> *(^)(void))createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to download a nil url"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
@ -251,6 +254,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
if (!operation) {
operation = createCallback();
if (!operation) {
UNLOCK(self.operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
}
return nil;
}
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
@ -412,6 +423,25 @@ didReceiveResponse:(NSURLResponse *)response
@implementation SDWebImageDownloadToken
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadReceiveResponseNotification object:nil];
}
- (instancetype)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:nil];
}
return self;
}
- (void)downloadReceiveResponse:(NSNotification *)notification {
NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
if (downloadOperation && downloadOperation == self.downloadOperation) {
self.response = downloadOperation.response;
}
}
- (void)cancel {
@synchronized (self) {
if (self.isCancelled) {

View File

@ -93,11 +93,6 @@ FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification
*/
@property (copy, nonatomic, readonly, nullable) SDWebImageContext *context;
/**
* The expected size of data.
*/
@property (assign, nonatomic, readonly) NSInteger expectedSize;
/**
* Initializes a `SDWebImageDownloaderOperation` object
*

View File

@ -46,7 +46,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (strong, nonatomic, nullable) NSMutableData *imageData;
@property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse`
@property (copy, nonatomic, nullable) NSString *cacheKey;
@property (assign, nonatomic, readwrite) NSInteger expectedSize;
@property (assign, nonatomic, readwrite) long long expectedSize;
@property (strong, nonatomic, nullable, readwrite) NSURLResponse *response;
// 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

View File

@ -20,6 +20,26 @@ typedef NSString * _Nullable(^SDWebImageCacheKeyFilterBlock)(NSURL * _Nullable u
typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull image, NSData * _Nullable data, NSURL * _Nullable imageURL);
// A combined operation representing the cache and download operation. You can it to cancel the load process.
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
/**
Cancel the current operation, including cache and download process
*/
- (void)cancel;
/**
The cache operation used for image cache query
*/
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> cacheOperation;
/**
The download operation if the image is download from the network
*/
@property (strong, nonatomic, nullable, readonly) id<SDWebImageOperation> downloadOperation;
@end
@class SDWebImageManager;
@ -173,12 +193,12 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
*
* The last parameter is the original image URL
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
* @return Returns an instance of SDWebImageCombinedOperation, which you can cancel the loading process.
*/
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
/**
* Downloads the image at the given URL if not present in cache or return the cached version otherwise.
@ -190,13 +210,13 @@ SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL * _Nullable url) {
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed.
*
* @return Returns an NSObject conforming to SDWebImageOperation. Should be an instance of SDWebImageDownloaderOperation
* @return Returns an instance of SDWebImageCombinedOperation, which you can cancel the loading process.
*/
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;
/**
* Saves image to cache for given URL

View File

@ -11,11 +11,11 @@
#import "UIImage+WebCache.h"
#import "SDAnimatedImage.h"
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@interface SDWebImageCombinedOperation ()
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;
@property (strong, nonatomic, nullable) SDWebImageDownloadToken *downloadToken;
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> downloadOperation;
@property (strong, nonatomic, readwrite, nullable) id<SDWebImageOperation> cacheOperation;
@property (weak, nonatomic, nullable) SDWebImageManager *manager;
@end
@ -108,15 +108,15 @@
}];
}
- (id<SDWebImageOperation>)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
return [self loadImageWithURL:url options:options context:nil progress:progressBlock completed:completedBlock];
}
- (id<SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");
@ -216,7 +216,7 @@
// `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle
__weak typeof(strongOperation) weakSubOperation = strongOperation;
strongOperation.downloadToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
strongOperation.downloadOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong typeof(weakSubOperation) strongSubOperation = weakSubOperation;
if (!strongSubOperation || strongSubOperation.isCancelled) {
// Do nothing if the operation was cancelled
@ -372,13 +372,17 @@
- (void)cancel {
@synchronized(self) {
if (self.isCancelled) {
return;
}
self.cancelled = YES;
if (self.cacheOperation) {
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.downloadToken) {
[self.manager.imageDownloader cancel:self.downloadToken];
if (self.downloadOperation) {
[self.downloadOperation cancel];
self.downloadOperation = nil;
}
[self.manager safelyRemoveOperationFromRunning:self];
}

View File

@ -16,6 +16,14 @@
// All the stored operations are weak, so it will be dalloced after image loading finished. If you need to store operations, use your own class to keep a strong reference for them.
@interface UIView (WebCacheOperation)
/**
* Get the image load operation for key
*
* @param key key for identifying the operations
* @return the image load operation
*/
- (nullable id<SDWebImageOperation>)sd_imageLoadOperationForKey:(nullable NSString *)key;
/**
* Set the image load operation (storage in a UIView based weak map table)
*

View File

@ -32,6 +32,15 @@ typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
}
}
- (nullable id<SDWebImageOperation>)sd_imageLoadOperationForKey:(nullable NSString *)key {
id<SDWebImageOperation> operation;
SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];
@synchronized (self) {
operation = [operationDictionary objectForKey:key];
}
return operation;
}
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {
if (key) {
[self sd_cancelImageLoadOperationWithKey:key];

View File

@ -109,10 +109,10 @@
- (void)test07ThatAddProgressCallbackCompletedBlockWithNilURLCallsTheCompletionBlockWithNils {
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion is called with nils"];
[[SDWebImageDownloader sharedDownloader] addProgressCallback:nil completedBlock:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
if (!image && !data && !error) {
if (!image && !data && error) {
[expectation fulfill];
} else {
XCTFail(@"All params should be nil");
XCTFail(@"All params except error should be nil");
}
} forURL:nil createCallback:nil];
[self waitForExpectationsWithTimeout:0.5 handler:nil];
@ -174,7 +174,7 @@
}];
expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
[[SDWebImageDownloader sharedDownloader] cancel:token];
[token cancel];
// doesn't cancel immediately - since it uses dispatch async
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMinDelayNanosecond), dispatch_get_main_queue(), ^{
@ -298,7 +298,7 @@
}];
expect(token2).toNot.beNil();
[[SDWebImageDownloader sharedDownloader] cancel:token1];
[token1 cancel];
[self waitForExpectationsWithCommonTimeout];
}
@ -323,7 +323,7 @@
}];
expect(token1).toNot.beNil();
[[SDWebImageDownloader sharedDownloader] cancel:token1];
[token1 cancel];
SDWebImageDownloadToken *token2 = [[SDWebImageDownloader sharedDownloader]
downloadImageWithURL:imageURL
@ -369,4 +369,49 @@
}
#endif
- (void)test23ThatDownloadRequestModifierWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Download request modifier not works"];
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
SDWebImageDownloaderRequestModifierBlock requestModifier = ^NSURLRequest *(NSURLRequest * request) {
if ([request.URL.absoluteString isEqualToString:kTestPNGURL]) {
// Test that return a modified request
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setValue:@"Bar" forHTTPHeaderField:@"Foo"];
NSURLComponents *components = [NSURLComponents componentsWithURL:mutableRequest.URL resolvingAgainstBaseURL:NO];
components.query = @"text=Hello+World";
mutableRequest.URL = components.URL;
return mutableRequest;
} else if ([request.URL.absoluteString isEqualToString:kTestJpegURL]) {
// Test that return nil request will treat as error
return nil;
} else {
return request;
}
};
downloader.requestModifier = requestModifier;
__block BOOL firstCheck = NO;
__block BOOL secondCheck = NO;
[downloader downloadImageWithURL:[NSURL URLWithString:kTestJpegURL] options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
// Except error
expect(error).notTo.beNil();
firstCheck = YES;
if (firstCheck && secondCheck) {
[expectation fulfill];
}
}];
[downloader downloadImageWithURL:[NSURL URLWithString:kTestPNGURL] options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
// Expect not error
expect(error).to.beNil();
secondCheck = YES;
if (firstCheck && secondCheck) {
[expectation fulfill];
}
}];
[self waitForExpectationsWithCommonTimeout];
}
@end