Skip to content

Commit

Permalink
Replace deprecated NSURLConnection with NSURLSession. See SDWebImage#…
Browse files Browse the repository at this point in the history
…1291 SDWebImage#1318 SDWebImage#823 SDWebImage#1566

- one session per operation to preserve the ordering and priority functionalities
- removed all the runloop code that was only used to keep the operation running. I am relying on non-setting the isFinished property to keep the operation running
- todo: check deallocation, test background tasks
  • Loading branch information
oanapopescu committed Jun 2, 2016
1 parent 86fc47b commit 5580c78
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 106 deletions.
7 changes: 0 additions & 7 deletions SDWebImage/SDWebImageDownloaderOperation.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@ extern NSString *const SDWebImageDownloadFinishNotification;

@property (assign, nonatomic) BOOL shouldDecompressImages;

/**
* Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
*
* This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`.
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;

/**
* The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
*
Expand Down
207 changes: 108 additions & 99 deletions SDWebImage/SDWebImageDownloaderOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";

@interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate>
@interface SDWebImageDownloaderOperation () <NSURLSessionTaskDelegate>

@property (copy, nonatomic) SDWebImageDownloaderProgressBlock progressBlock;
@property (copy, nonatomic) SDWebImageDownloaderCompletedBlock completedBlock;
Expand All @@ -26,7 +26,10 @@ @interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate>
@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, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionDataTask *dataTask;

@property (strong, atomic) NSThread *thread;

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Expand All @@ -52,19 +55,22 @@ - (id)initWithRequest:(NSURLRequest *)request
if ((self = [super init])) {
_request = request;
_shouldDecompressImages = YES;
_shouldUseCredentialStorage = YES;
_options = options;
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO;
_finished = NO;
_expectedSize = 0;
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
}
return self;
}

- (void)dealloc {

}

- (void)start {
@synchronized (self) {
if (self.isCancelled) {
Expand All @@ -91,36 +97,26 @@ - (void)start {
}];
}
#endif

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
self.session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];

self.executing = YES;
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
self.dataTask = [self.session dataTaskWithRequest:self.request];
self.thread = [NSThread currentThread];
}

[self.dataTask resume];

[self.connection start];

if (self.connection) {
if (self.dataTask) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});

if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
// Make sure to run the runloop in our background thread so it can process downloaded data
// Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
// not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}

if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
if (self.completedBlock) {
Expand Down Expand Up @@ -155,16 +151,15 @@ - (void)cancel {
- (void)cancelInternalAndStop {
if (self.isFinished) return;
[self cancelInternal];
CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)cancelInternal {
if (self.isFinished) return;
[super cancel];
if (self.cancelBlock) self.cancelBlock();

if (self.connection) {
[self.connection cancel];
if (self.dataTask) {
[self.dataTask cancel];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
Expand All @@ -188,9 +183,11 @@ - (void)reset {
self.cancelBlock = nil;
self.completedBlock = nil;
self.progressBlock = nil;
self.connection = nil;
self.dataTask = nil;
self.imageData = nil;
self.thread = nil;
[self.session invalidateAndCancel];
self.session = nil;
}

- (void)setFinished:(BOOL)finished {
Expand All @@ -209,9 +206,12 @@ - (BOOL)isConcurrent {
return YES;
}

#pragma mark NSURLConnection (delegate)
#pragma mark NSURLSessionTaskDelegate

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
Expand All @@ -220,7 +220,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon
if (self.progressBlock) {
self.progressBlock(0, expected);
}

self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
Expand All @@ -235,21 +235,24 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon
if (code == 304) {
[self cancelInternal];
} else {
[self.connection cancel];
[self.dataTask cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});

if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
}

if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data];

if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
Expand Down Expand Up @@ -277,7 +280,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// When we draw to Core Graphics, we lose orientation information,
// which means the image below born of initWithCGIImage will be
// oriented incorrectly sometimes. (Unlike the image born of initWithData
// in connectionDidFinishLoading.) So save it here and pass it on later.
// in didCompleteWithError.) So save it here and pass it on later.
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}

Expand Down Expand Up @@ -361,106 +364,112 @@ - (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
return SDScaledImageForKey(key, image);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
self.dataTask = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
}
});
}

if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
responseFromCached = NO;
}

if (completionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];

// Do not force decoding animated GIFs
if (!image.images) {
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
if (error) {
if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
}
} else {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;

if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
responseFromCached = NO;
}

if (completionBlock) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];

// Do not force decoding animated GIFs
if (!image.images) {
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
}
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 {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
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 {
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
}

self.completionBlock = nil;
[self done];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
@synchronized(self) {
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
}

if (self.completedBlock) {
self.completedBlock(nil, nil, error, YES);
}
self.completionBlock = nil;
[self done];
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
- (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
return nil;
cachedResponse = nil;
}
else {
return cachedResponse;
if (completionHandler) {
completionHandler(cachedResponse);
}
}

- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
return self.shouldUseCredentialStorage;
}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {

NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates) &&
[challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if ([challenge previousFailureCount] == 0) {
if (self.credential) {
[[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}

if (completionHandler) {
completionHandler(disposition, credential);
}
}

@end

0 comments on commit 5580c78

Please sign in to comment.