Merge pull request #1586 from oanapopescu/nsurlsession

Updated image downloader with a single session that manages all tasks
This commit is contained in:
Bogdan Poplauschi 2016-06-06 21:00:34 +03:00
commit 5d42a2ac5e
3 changed files with 207 additions and 70 deletions

View File

@ -13,7 +13,7 @@
static NSString *const kProgressCallbackKey = @"progress"; static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed"; static NSString *const kCompletedCallbackKey = @"completed";
@interface SDWebImageDownloader () @interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (strong, nonatomic) NSOperationQueue *downloadQueue; @property (strong, nonatomic) NSOperationQueue *downloadQueue;
@property (weak, nonatomic) NSOperation *lastAddedOperation; @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 // 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; @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@end @end
@implementation SDWebImageDownloader @implementation SDWebImageDownloader
@ -74,11 +77,26 @@ static NSString *const kCompletedCallbackKey = @"completed";
#endif #endif
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0; _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; return self;
} }
- (void)dealloc { - (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations]; [self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue); SDDispatchQueueRelease(_barrierQueue);
} }
@ -133,6 +151,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
request.allHTTPHeaderFields = wself.HTTPHeaders; request.allHTTPHeaderFields = wself.HTTPHeaders;
} }
operation = [[wself.operationClass alloc] initWithRequest:request operation = [[wself.operationClass alloc] initWithRequest:request
inSession:self.session
options:options options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) { progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself; SDWebImageDownloader *sself = wself;
@ -233,4 +252,66 @@ static NSString *const kCompletedCallbackKey = @"completed";
[self.downloadQueue cancelAllOperations]; [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 @end

View File

@ -15,13 +15,18 @@ extern NSString *const SDWebImageDownloadReceiveResponseNotification;
extern NSString *const SDWebImageDownloadStopNotification; extern NSString *const SDWebImageDownloadStopNotification;
extern NSString *const SDWebImageDownloadFinishNotification; 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; @property (strong, nonatomic, readonly) NSURLRequest *request;
/**
* The operation's task
*/
@property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
@property (assign, nonatomic) BOOL shouldDecompressImages; @property (assign, nonatomic) BOOL shouldDecompressImages;
@ -53,6 +58,7 @@ extern NSString *const SDWebImageDownloadFinishNotification;
* @see SDWebImageDownloaderOperation * @see SDWebImageDownloaderOperation
* *
* @param request the URL request * @param request the URL request
* @param session the URL session in which this operation will run
* @param options downloader options * @param options downloader options
* @param progressBlock the block executed when a new chunk of data arrives. * @param progressBlock the block executed when a new chunk of data arrives.
* @note the progress block is executed on a background queue * @note the progress block is executed on a background queue
@ -63,9 +69,32 @@ extern NSString *const SDWebImageDownloadFinishNotification;
* @return the initialized instance * @return the initialized instance
*/ */
- (id)initWithRequest:(NSURLRequest *)request - (id)initWithRequest:(NSURLRequest *)request
inSession:(NSURLSession *)session
options:(SDWebImageDownloaderOptions)options options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock; 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 @end

View File

@ -17,7 +17,7 @@ NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDown
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
@interface SDWebImageDownloaderOperation () <NSURLSessionTaskDelegate> @interface SDWebImageDownloaderOperation ()
@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock; @property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock; @property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
@ -27,8 +27,13 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
@property (assign, nonatomic, getter = isFinished) BOOL finished; @property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic) NSMutableData *imageData; @property (strong, nonatomic) NSMutableData *imageData;
@property (strong, nonatomic) NSURLSession *session; // 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
@property (strong, nonatomic) NSURLSessionDataTask *dataTask; // 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; @property (strong, atomic) NSThread *thread;
@ -52,6 +57,21 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
progress:(SDWebImageDownloaderProgressBlock)progressBlock progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock { 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])) { if ((self = [super init])) {
_request = request; _request = request;
_shouldDecompressImages = YES; _shouldDecompressImages = YES;
@ -62,6 +82,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
_executing = NO; _executing = NO;
_finished = NO; _finished = NO;
_expectedSize = 0; _expectedSize = 0;
_unownedSession = session;
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
} }
return self; return self;
@ -93,20 +114,24 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
}]; }];
} }
#endif #endif
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = self.unownedSession;
sessionConfig.timeoutIntervalForRequest = 15; 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. * 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
self.session = [NSURLSession sessionWithConfiguration:sessionConfig * method calls and completion handler calls.
delegate:self */
delegateQueue:nil]; self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES; self.executing = YES;
self.dataTask = [self.session dataTaskWithRequest:self.request];
self.thread = [NSThread currentThread]; self.thread = [NSThread currentThread];
} }
@ -188,8 +213,10 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
self.dataTask = nil; self.dataTask = nil;
self.imageData = nil; self.imageData = nil;
self.thread = nil; self.thread = nil;
[self.session invalidateAndCancel]; if (self.ownedSession) {
self.session = nil; [self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
} }
- (void)setFinished:(BOOL)finished { - (void)setFinished:(BOOL)finished {
@ -208,7 +235,7 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
return YES; return YES;
} }
#pragma mark NSURLSessionTaskDelegate #pragma mark NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session - (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask dataTask:(NSURLSessionDataTask *)dataTask
@ -339,32 +366,24 @@ didReceiveResponse:(NSURLResponse *)response
} }
} }
+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value { - (void)URLSession:(NSURLSession *)session
switch (value) { dataTask:(NSURLSessionDataTask *)dataTask
case 1: willCacheResponse:(NSCachedURLResponse *)proposedResponse
return UIImageOrientationUp; completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
case 3:
return UIImageOrientationDown; responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
case 8: NSCachedURLResponse *cachedResponse = proposedResponse;
return UIImageOrientationLeft;
case 6: if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
return UIImageOrientationRight; // Prevents caching of responses
case 2: cachedResponse = nil;
return UIImageOrientationUpMirrored; }
case 4: if (completionHandler) {
return UIImageOrientationDownMirrored; completionHandler(cachedResponse);
case 5:
return UIImageOrientationLeftMirrored;
case 7:
return UIImageOrientationRightMirrored;
default:
return UIImageOrientationUp;
} }
} }
- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image { #pragma mark NSURLSessionTaskDelegate
return SDScaledImageForKey(key, image);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) { @synchronized(self) {
@ -419,32 +438,7 @@ didReceiveResponse:(NSURLResponse *)response
[self done]; [self done];
} }
- (void)URLSession:(NSURLSession *)session - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
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 {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil; __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 @end