Fix multiple requests for same image and then canceling one
This commit is contained in:
parent
6a623cd58f
commit
917c3d479d
|
@ -176,12 +176,19 @@ 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
|
||||
options:(SDWebImageDownloaderOptions)options
|
||||
progress:(SDWebImageDownloaderProgressBlock)progressBlock
|
||||
completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
|
||||
- (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
|
||||
|
|
|
@ -13,6 +13,16 @@
|
|||
static NSString *const kProgressCallbackKey = @"progress";
|
||||
static NSString *const kCompletedCallbackKey = @"completed";
|
||||
|
||||
@interface _SDWebImageDownloaderToken : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSURL *url;
|
||||
@property (nonatomic, strong) id callbacks;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _SDWebImageDownloaderToken
|
||||
@end
|
||||
|
||||
@interface SDWebImageDownloader ()
|
||||
|
||||
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
|
||||
|
@ -112,11 +122,11 @@ static NSString *const kCompletedCallbackKey = @"completed";
|
|||
_operationClass = operationClass ?: [SDWebImageDownloaderOperation class];
|
||||
}
|
||||
|
||||
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
|
||||
- (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
|
||||
__block SDWebImageDownloaderOperation *operation;
|
||||
__weak __typeof(self)wself = self;
|
||||
__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;
|
||||
|
@ -190,20 +200,34 @@ static NSString *const kCompletedCallbackKey = @"completed";
|
|||
[wself.lastAddedOperation addDependency:operation];
|
||||
wself.lastAddedOperation = operation;
|
||||
}
|
||||
}];
|
||||
|
||||
return 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;
|
||||
NSMutableArray *callbacksForURL = self.URLCallbacks[typedToken.url];
|
||||
[callbacksForURL removeObjectIdenticalTo:typedToken.callbacks];
|
||||
});
|
||||
}
|
||||
|
||||
- (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]) {
|
||||
|
@ -222,7 +246,13 @@ static NSString *const kCompletedCallbackKey = @"completed";
|
|||
if (first) {
|
||||
createCallback();
|
||||
}
|
||||
|
||||
token = [_SDWebImageDownloaderToken new];
|
||||
token.url = url;
|
||||
token.callbacks = callbacks;
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
- (void)setSuspended:(BOOL)suspended {
|
||||
|
|
|
@ -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,8 +255,8 @@
|
|||
}
|
||||
}];
|
||||
operation.cancelBlock = ^{
|
||||
[subOperation cancel];
|
||||
|
||||
[self.imageDownloader cancel:subOperation];
|
||||
|
||||
@synchronized (self.runningOperations) {
|
||||
__strong __typeof(weakOperation) strongOperation = weakOperation;
|
||||
if (strongOperation) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */; };
|
||||
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
|
||||
ABC8501F672447AA91C788DA /* libPods-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EB0D107E6B4C4094BA2FEE29 /* libPods-ios.a */; };
|
||||
DA248D57195472AA00390AB0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D56195472AA00390AB0 /* XCTest.framework */; };
|
||||
DA248D59195472AA00390AB0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D58195472AA00390AB0 /* Foundation.framework */; };
|
||||
|
@ -22,6 +23,7 @@
|
|||
1A6DF883515E8008203AB352 /* Pods-ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ios/Pods-ios.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = TestImage.jpg; sourceTree = "<group>"; };
|
||||
CA88E6BDE3581B2BFE933C10 /* Pods-ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios.release.xcconfig"; path = "Pods/Target Support Files/Pods-ios/Pods-ios.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>";
|
||||
|
@ -207,6 +210,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 */,
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// 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 {
|
||||
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();
|
||||
|
||||
__block BOOL success = NO;
|
||||
id token2 = [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL
|
||||
options:0
|
||||
progress:nil
|
||||
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
|
||||
success = YES;
|
||||
}];
|
||||
expect(token2).toNot.beNil();
|
||||
|
||||
[[SDWebImageDownloader sharedDownloader] cancel:token1];
|
||||
|
||||
CFTimeInterval timeoutDate = CACurrentMediaTime() + 5.;
|
||||
while (true) {
|
||||
if (CACurrentMediaTime() > timeoutDate || success) {
|
||||
break;
|
||||
}
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1., true);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
XCTFail(@"Failed to download image");
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
Loading…
Reference in New Issue