Authored by Bogdan Poplauschi

Merge pull request #1586 from oanapopescu/nsurlsession

Updated image downloader with a single session that manages all tasks
... ... @@ -13,7 +13,7 @@
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloader ()
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
@property (weak, nonatomic) NSOperation *lastAddedOperation;
... ... @@ -23,6 +23,9 @@ static NSString *const kCompletedCallbackKey = @"completed";
// 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;
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
... ... @@ -74,11 +77,26 @@ static NSString *const kCompletedCallbackKey = @"completed";
#endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = _downloadTimeout;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
}
return self;
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
... ... @@ -133,6 +151,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
operation = [[wself.operationClass alloc] initWithRequest:request
inSession:self.session
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
... ... @@ -233,4 +252,66 @@ static NSString *const kCompletedCallbackKey = @"completed";
[self.downloadQueue cancelAllOperations];
}
#pragma mark Helper methods
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
return returnOperation;
}
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
@end
... ...
... ... @@ -15,13 +15,18 @@ extern NSString *const SDWebImageDownloadReceiveResponseNotification;
extern NSString *const SDWebImageDownloadStopNotification;
extern NSString *const SDWebImageDownloadFinishNotification;
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation>
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
/**
* The request used by the operation's connection.
* The request used by the operation's task.
*/
@property (strong, nonatomic, readonly) NSURLRequest *request;
/**
* The operation's task
*/
@property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
@property (assign, nonatomic) BOOL shouldDecompressImages;
... ... @@ -53,6 +58,7 @@ extern NSString *const SDWebImageDownloadFinishNotification;
* @see SDWebImageDownloaderOperation
*
* @param request the URL request
* @param session the URL session in which this operation will run
* @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
... ... @@ -63,9 +69,32 @@ extern NSString *const SDWebImageDownloadFinishNotification;
* @return the initialized instance
*/
- (id)initWithRequest:(NSURLRequest *)request
inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
/**
* Initializes a `SDWebImageDownloaderOperation` object
*
* @see SDWebImageDownloaderOperation
*
* @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. The operation will run in a separate session created for this operation
*/
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock
__deprecated_msg("Method deprecated. Use `initWithRequest:inSession:options:progress:completed:cancelled`");
@end
... ...
... ... @@ -17,7 +17,7 @@ NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDown
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
@interface SDWebImageDownloaderOperation () <NSURLSessionTaskDelegate>
@interface SDWebImageDownloaderOperation ()
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
... ... @@ -27,8 +27,13 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionDataTask *dataTask;
// 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
@property (weak, nonatomic) NSURLSession *unownedSession;
// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
@property (strong, nonatomic) NSURLSession *ownedSession;
@property (strong, nonatomic, readwrite) NSURLSessionTask *dataTask;
@property (strong, atomic) NSThread *thread;
... ... @@ -52,6 +57,21 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
return [self initWithRequest:request
inSession:nil
options:options
progress:progressBlock
completed:completedBlock
cancelled:cancelBlock];
}
- (id)initWithRequest:(NSURLRequest *)request
inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) {
_request = request;
_shouldDecompressImages = YES;
... ... @@ -62,6 +82,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
}
return self;
... ... @@ -93,20 +114,24 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}];
}
#endif
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
self.dataTask = [self.session dataTaskWithRequest:self.request];
self.thread = [NSThread currentThread];
}
... ... @@ -188,8 +213,10 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
self.dataTask = nil;
self.imageData = nil;
self.thread = nil;
[self.session invalidateAndCancel];
self.session = nil;
if (self.ownedSession) {
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
- (void)setFinished:(BOOL)finished {
... ... @@ -208,7 +235,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
return YES;
}
#pragma mark NSURLSessionTaskDelegate
#pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
... ... @@ -339,32 +366,24 @@ didReceiveResponse:(NSURLResponse *)response
}
}
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
... ... @@ -419,32 +438,7 @@ didReceiveResponse:(NSURLResponse *)response
[self done];
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
... ... @@ -474,4 +468,37 @@ didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
}
}
#pragma mark Helper methods
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
switch (value) {
case 1:
return UIImageOrientationUp;
case 3:
return UIImageOrientationDown;
case 8:
return UIImageOrientationLeft;
case 6:
return UIImageOrientationRight;
case 2:
return UIImageOrientationUpMirrored;
case 4:
return UIImageOrientationDownMirrored;
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
}
}
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
@end
... ...