Merge pull request #883 from mattjgalloway/fix_multiple_downloads

Fix multiple requests for same image and then canceling one
This commit is contained in:
Bogdan Poplauschi 2016-05-23 07:13:01 +03:00
commit 13cfc5b89a
7 changed files with 266 additions and 122 deletions

View File

@ -176,13 +176,20 @@ typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDi
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A cancellable SDWebImageOperation
* @return A token that can be passed to -cancel: to cancel this operation
*/
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
- (id)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(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:(id)token;
/**
* Sets the download queue suspension state
*/

View File

@ -10,15 +10,22 @@
#import "SDWebImageDownloaderOperation.h"
#import <ImageIO/ImageIO.h>
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@interface _SDWebImageDownloaderToken : NSObject
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) id downloadOperationCancelToken;
@end
@implementation _SDWebImageDownloaderToken
@end
@interface SDWebImageDownloader ()
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
@property (weak, nonatomic) NSOperation *lastAddedOperation;
@property (assign, nonatomic) Class operationClass;
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
@property (strong, nonatomic) NSMutableDictionary *URLOperations;
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
@ -66,7 +73,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_URLCallbacks = [NSMutableDictionary new];
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
@ -112,11 +119,10 @@ static NSString *const kCompletedCallbackKey = @"completed";
_operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
}
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
- (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
@ -132,44 +138,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
SDWebImageDownloaderOperation *operation = [[wself.operationClass alloc] initWithRequest:request options:options];
operation.shouldDecompressImages = wself.shouldDecompressImages;
if (wself.urlCredential) {
@ -190,39 +159,60 @@ static NSString *const kCompletedCallbackKey = @"completed";
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}];
}
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
- (void)cancel:(id)token {
if (![token isKindOfClass:[_SDWebImageDownloaderToken class]]) {
return;
}
dispatch_barrier_async(self.barrierQueue, ^{
_SDWebImageDownloaderToken *typedToken = (_SDWebImageDownloaderToken *)token;
SDWebImageDownloaderOperation *operation = self.URLOperations[typedToken.url];
BOOL canceled = [operation cancel:typedToken.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:typedToken.url];
}
});
}
- (id)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageDownloaderOperation *(^)())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);
}
return;
return nil;
}
__block _SDWebImageDownloaderToken *token = nil;
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
self.URLOperations[url] = operation;
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [_SDWebImageDownloaderToken new];
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
});
return token;
}
- (void)setSuspended:(BOOL)suspended {

View File

@ -61,18 +61,33 @@ extern NSString *const SDWebImageDownloadFinishNotification;
*
* @param request the URL request
* @param options downloader options
* @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
* @param cancelBlock the block executed if the download (operation) is cancelled
*
* @return the initialized instance
*/
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
options:(SDWebImageDownloaderOptions)options;
/**
* Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of
* callbacks.
*
* @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue
* @param completedBlock the block executed when the download is done.
* @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue
*
* @return the token to use to cancel this set of handlers
*/
- (id)addHandlersForProgress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a set of callbacks. Once all callbacks are canceled, the operation is cancelled.
*
* @param token the token representing a set of callbacks to cancel
*
* @return YES if the operation was stopped because this was the last token to be canceled. NO otherwise.
*/
- (BOOL)cancel:(id)token;
@end

View File

@ -17,17 +17,19 @@ NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDown
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate>
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;
@property (strong, nonatomic) NSMutableArray *callbackBlocks;
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, atomic) NSThread *thread;
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
@ -45,26 +47,61 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
@synthesize finished = _finished;
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = request;
_shouldDecompressImages = YES;
_shouldUseCredentialStorage = YES;
_options = options;
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
- (id)addHandlersForProgress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
}
- (NSArray *)callbacksForKey:(NSString *)key {
__block NSMutableArray *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return callbacks;
}
- (BOOL)cancel:(id)token {
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
@ -100,8 +137,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
[self.connection start];
if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
@ -123,8 +160,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}
}
else {
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
@ -161,7 +198,6 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.cancelBlock) self.cancelBlock();
if (self.connection) {
[self.connection cancel];
@ -185,9 +221,9 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}
- (void)reset {
self.cancelBlock = nil;
self.completedBlock = nil;
self.progressBlock = nil;
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
self.connection = nil;
self.imageData = nil;
self.thread = nil;
@ -217,8 +253,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
if (self.progressBlock) {
self.progressBlock(0, expected);
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
@ -241,8 +277,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
@ -252,7 +288,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
@ -319,8 +355,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}
CGImageRelease(partialImageRef);
dispatch_main_sync_safe(^{
if (self.completedBlock) {
self.completedBlock(image, nil, nil, NO);
for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
completedBlock(image, nil, nil, NO);
}
});
}
@ -329,8 +365,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
CFRelease(imageSource);
}
if (self.progressBlock) {
self.progressBlock(self.imageData.length, self.expectedSize);
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize);
}
}
@ -362,7 +398,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
NSArray *completionBlocks = [[self callbacksForKey:kCompletedCallbackKey] copy];
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
@ -377,9 +413,11 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
responseFromCached = NO;
}
if (completionBlock) {
if (completionBlocks.count > 0) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
completionBlock(nil, nil, nil, YES);
}
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
@ -391,17 +429,20 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
image = [UIImage decodedImageWithImage:image];
}
}
for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
completionBlock(image, self.imageData, nil, YES);
}
}
} else {
for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
}
self.completionBlock = nil;
}
[self done];
}
@ -415,8 +456,8 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
});
}
if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
completedBlock(nil, nil, error, YES);
}
self.completionBlock = nil;
[self done];

View File

@ -179,7 +179,7 @@
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
@ -255,7 +255,7 @@
}
}];
operation.cancelBlock = ^{
[subOperation cancel];
[self.imageDownloader cancel:subOperation];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
0D87E1F83BD319CEC7622E9F /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0462A7F023A057322E59B3C5 /* libPods-Tests.a */; };
5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */; };
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
DA248D57195472AA00390AB0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D56195472AA00390AB0 /* XCTest.framework */; };
DA248D59195472AA00390AB0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D58195472AA00390AB0 /* Foundation.framework */; };
DA248D5B195472AA00390AB0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D5A195472AA00390AB0 /* UIKit.framework */; };
@ -23,6 +24,7 @@
5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = TestImage.jpg; sourceTree = "<group>"; };
700B00151041D7EE118B1ABD /* Pods-Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.debug.xcconfig"; sourceTree = "<group>"; };
A0085854E7D88C98F2F6C9FC /* Pods-Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests/Pods-Tests.release.xcconfig"; sourceTree = "<group>"; };
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = "<group>"; };
DA248D53195472AA00390AB0 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DA248D56195472AA00390AB0 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
DA248D58195472AA00390AB0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
@ -96,6 +98,7 @@
DA248D68195475D800390AB0 /* SDImageCacheTests.m */,
DA248D6A195476AC00390AB0 /* SDWebImageManagerTests.m */,
DA91BEBB19795BC9006F2536 /* UIImageMultiFormatTests.m */,
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */,
);
path = Tests;
sourceTree = "<group>";
@ -223,6 +226,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */,
DA248D69195475D800390AB0 /* SDImageCacheTests.m in Sources */,
DA248D6B195476AC00390AB0 /* SDWebImageManagerTests.m in Sources */,
DA91BEBC19795BC9006F2536 /* UIImageMultiFormatTests.m in Sources */,

View File

@ -0,0 +1,87 @@
//
// SDWebImageDownloaderTests.m
// SDWebImage Tests
//
// Created by Matt Galloway on 01/09/2014.
//
//
#define EXP_SHORTHAND // required by Expecta
#import <XCTest/XCTest.h>
#import <Expecta.h>
#import "SDWebImageDownloader.h"
@interface SDWebImageDownloaderTests : XCTestCase
@end
@implementation SDWebImageDownloaderTests
- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown
{
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testThatDownloadingSameURLTwiceAndCancellingFirstWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Correct image downloads"];
NSURL *imageURL = [NSURL URLWithString:@"http://static2.dmcdn.net/static/video/656/177/44771656:jpeg_preview_small.jpg?20120509154705"];
id token1 = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
XCTFail(@"Shouldn't have completed here.");
}];
expect(token1).toNot.beNil();
id token2 = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
[expectation fulfill];
}];
expect(token2).toNot.beNil();
[[SDWebImageDownloader sharedDownloader] cancel:token1];
[self waitForExpectationsWithTimeout:5. handler:nil];
}
- (void)testThatCancelingDownloadThenRequestingAgainWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Correct image downloads"];
NSURL *imageURL = [NSURL URLWithString:@"http://static2.dmcdn.net/static/video/656/177/44771656:jpeg_preview_small.jpg?20120509154705"];
id token1 = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
XCTFail(@"Shouldn't have completed here.");
}];
expect(token1).toNot.beNil();
[[SDWebImageDownloader sharedDownloader] cancel:token1];
id token2 = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
[expectation fulfill];
}];
expect(token2).toNot.beNil();
[self waitForExpectationsWithTimeout:5. handler:nil];
}
@end