Authored by Bogdan Poplauschi

Merge branch 'master' into 4.x

+ SDWebImageDownloader downloadImageWithURL:... properly use the weakself-strongself pattern

# Conflicts:
#	.travis.yml
#	Examples/SDWebImage Demo.xcodeproj/project.pbxproj
#	README.md
#	SDWebImage.xcodeproj/project.pbxproj
#	SDWebImage/SDWebImageDownloader.m
#	SDWebImage/SDWebImageDownloaderOperation.h
#	SDWebImage/SDWebImageDownloaderOperation.m
#	SDWebImage/SDWebImageManager.m
#	Tests/Podfile
#	Tests/SDWebImage Tests.xcodeproj/project.pbxproj
@@ -39,4 +39,4 @@ script: @@ -39,4 +39,4 @@ script:
39 - echo Run the tests 39 - echo Run the tests
40 - pod install --project-directory=Tests 40 - pod install --project-directory=Tests
41 - xcodebuild clean -workspace SDWebImage.xcworkspace -scheme 'SDWebImage-static' -sdk iphonesimulator PLATFORM_NAME=iphonesimulator -configuration Debug | xcpretty -c 41 - xcodebuild clean -workspace SDWebImage.xcworkspace -scheme 'SDWebImage-static' -sdk iphonesimulator PLATFORM_NAME=iphonesimulator -configuration Debug | xcpretty -c
42 - - xcodebuild test -workspace SDWebImage.xcworkspace -scheme 'Tests' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' -configuration Debug | xcpretty -c  
  42 + - xcodebuild test -workspace SDWebImage.xcworkspace -scheme 'Tests' -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' -configuration Debug | xcpretty -c
  1 +## [3.8.0 Minor release - Replaces NSURLConnection (deprecated) with NSURLSession - on Jun 6th, 2016](https://github.com/rs/SDWebImage/releases/tag/3.8.0)
  2 +
  3 +#### Infrastructure:
  4 +
  5 +- Had to update the iOS deployment target to 7.0 from 5 - 6545a3a
  6 +
  7 +#### Features:
  8 +
  9 +- Replace deprecated `NSURLConnection` with `NSURLSession` #1578 #1586 - fixes #1291 #1318 #823 #1566 #1515
  10 +- Allow to customise cache and image downloader instances used with `SDWebImageManager` 86fc47bf7b - fixes #1398 #870
  11 +
  12 +#### Fixes:
  13 +
  14 +- Removed the URL query params from the filename (key) fb0cdb6d 1bf62d4 #1584 - fixes #1433 #1533 #1583 #1585
  15 +- Fixed the WebP build with the official 1.0.0 CocoaPods release f1a471e - fixes #1444
  16 +- Updated doc: `removeImageForKey:` not synchronous e6e5c51 - fixes #1379 #1415
  17 +
1 ## [3.7.6 Patch release for 3.7.0 on May 8th, 2016](https://github.com/rs/SDWebImage/releases/tag/3.7.6) 18 ## [3.7.6 Patch release for 3.7.0 on May 8th, 2016](https://github.com/rs/SDWebImage/releases/tag/3.7.6)
2 19
3 #### Infrastructure: 20 #### Infrastructure:
@@ -323,7 +323,7 @@ @@ -323,7 +323,7 @@
323 GCC_WARN_ABOUT_RETURN_TYPE = YES; 323 GCC_WARN_ABOUT_RETURN_TYPE = YES;
324 GCC_WARN_UNINITIALIZED_AUTOS = YES; 324 GCC_WARN_UNINITIALIZED_AUTOS = YES;
325 GCC_WARN_UNUSED_VARIABLE = YES; 325 GCC_WARN_UNUSED_VARIABLE = YES;
326 - IPHONEOS_DEPLOYMENT_TARGET = 6.0; 326 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
327 ONLY_ACTIVE_ARCH = YES; 327 ONLY_ACTIVE_ARCH = YES;
328 SDKROOT = iphoneos; 328 SDKROOT = iphoneos;
329 }; 329 };
@@ -341,7 +341,7 @@ @@ -341,7 +341,7 @@
341 GCC_WARN_ABOUT_RETURN_TYPE = YES; 341 GCC_WARN_ABOUT_RETURN_TYPE = YES;
342 GCC_WARN_UNINITIALIZED_AUTOS = YES; 342 GCC_WARN_UNINITIALIZED_AUTOS = YES;
343 GCC_WARN_UNUSED_VARIABLE = YES; 343 GCC_WARN_UNUSED_VARIABLE = YES;
344 - IPHONEOS_DEPLOYMENT_TARGET = 6.0; 344 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
345 OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 345 OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
346 SDKROOT = iphoneos; 346 SDKROOT = iphoneos;
347 VALIDATE_PRODUCT = YES; 347 VALIDATE_PRODUCT = YES;
@@ -120,8 +120,8 @@ There are three ways to use SDWebImage in your project: @@ -120,8 +120,8 @@ There are three ways to use SDWebImage in your project:
120 120
121 #### Podfile 121 #### Podfile
122 ``` 122 ```
123 -platform :ios, '6.1'  
124 -pod 'SDWebImage', '~>3.7' 123 +platform :ios, '7.0'
  124 +pod 'SDWebImage', '~>3.8'
125 ``` 125 ```
126 126
127 If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+: 127 If you are using Swift, be sure to add `use_frameworks!` and set your target to iOS 8+:
1 Pod::Spec.new do |s| 1 Pod::Spec.new do |s|
2 s.name = 'SDWebImage' 2 s.name = 'SDWebImage'
3 - s.version = '3.7.6'  
4 - s.ios.deployment_target = '5.0' 3 + s.version = '3.8.0'
  4 + s.ios.deployment_target = '7.0'
5 s.tvos.deployment_target = '9.0' 5 s.tvos.deployment_target = '9.0'
6 s.license = 'MIT' 6 s.license = 'MIT'
7 s.summary = 'Asynchronous image downloader with cache support with an UIImageView category.' 7 s.summary = 'Asynchronous image downloader with cache support with an UIImageView category.'
@@ -29,7 +29,7 @@ Pod::Spec.new do |s| @@ -29,7 +29,7 @@ Pod::Spec.new do |s|
29 end 29 end
30 30
31 s.subspec 'MapKit' do |mk| 31 s.subspec 'MapKit' do |mk|
32 - mk.ios.deployment_target = '5.0' 32 + mk.ios.deployment_target = '7.0'
33 mk.source_files = 'SDWebImage/MKAnnotationView+WebCache.*' 33 mk.source_files = 'SDWebImage/MKAnnotationView+WebCache.*'
34 mk.framework = 'MapKit' 34 mk.framework = 'MapKit'
35 mk.dependency 'SDWebImage/Core' 35 mk.dependency 'SDWebImage/Core'
@@ -1396,7 +1396,7 @@ @@ -1396,7 +1396,7 @@
1396 GCC_WARN_UNUSED_PARAMETER = NO; 1396 GCC_WARN_UNUSED_PARAMETER = NO;
1397 GCC_WARN_UNUSED_VARIABLE = YES; 1397 GCC_WARN_UNUSED_VARIABLE = YES;
1398 HEADER_SEARCH_PATHS = Vendors/libwebp/src; 1398 HEADER_SEARCH_PATHS = Vendors/libwebp/src;
1399 - IPHONEOS_DEPLOYMENT_TARGET = 5.0; 1399 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
1400 ONLY_ACTIVE_ARCH = YES; 1400 ONLY_ACTIVE_ARCH = YES;
1401 OTHER_LDFLAGS = "-ObjC"; 1401 OTHER_LDFLAGS = "-ObjC";
1402 RUN_CLANG_STATIC_ANALYZER = YES; 1402 RUN_CLANG_STATIC_ANALYZER = YES;
@@ -1451,7 +1451,7 @@ @@ -1451,7 +1451,7 @@
1451 GCC_WARN_UNUSED_PARAMETER = NO; 1451 GCC_WARN_UNUSED_PARAMETER = NO;
1452 GCC_WARN_UNUSED_VARIABLE = YES; 1452 GCC_WARN_UNUSED_VARIABLE = YES;
1453 HEADER_SEARCH_PATHS = Vendors/libwebp/src; 1453 HEADER_SEARCH_PATHS = Vendors/libwebp/src;
1454 - IPHONEOS_DEPLOYMENT_TARGET = 5.0; 1454 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
1455 OTHER_LDFLAGS = "-ObjC"; 1455 OTHER_LDFLAGS = "-ObjC";
1456 RUN_CLANG_STATIC_ANALYZER = YES; 1456 RUN_CLANG_STATIC_ANALYZER = YES;
1457 SDKROOT = iphoneos; 1457 SDKROOT = iphoneos;
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 @end 14 @end
15 15
16 16
17 -@interface SDWebImageDownloader () 17 +@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
18 18
19 @property (strong, nonatomic) NSOperationQueue *downloadQueue; 19 @property (strong, nonatomic) NSOperationQueue *downloadQueue;
20 @property (weak, nonatomic) NSOperation *lastAddedOperation; 20 @property (weak, nonatomic) NSOperation *lastAddedOperation;
@@ -24,6 +24,9 @@ @@ -24,6 +24,9 @@
24 // This queue is used to serialize the handling of the network responses of all the download operation in a single queue 24 // This queue is used to serialize the handling of the network responses of all the download operation in a single queue
25 @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue; 25 @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
26 26
  27 +// The session in which data tasks will run
  28 +@property (strong, nonatomic) NSURLSession *session;
  29 +
27 @end 30 @end
28 31
29 @implementation SDWebImageDownloader 32 @implementation SDWebImageDownloader
@@ -75,11 +78,26 @@ @@ -75,11 +78,26 @@
75 #endif 78 #endif
76 _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); 79 _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
77 _downloadTimeout = 15.0; 80 _downloadTimeout = 15.0;
  81 +
  82 + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  83 + sessionConfig.timeoutIntervalForRequest = _downloadTimeout;
  84 +
  85 + /**
  86 + * Create the session for this task
  87 + * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  88 + * method calls and completion handler calls.
  89 + */
  90 + self.session = [NSURLSession sessionWithConfiguration:sessionConfig
  91 + delegate:self
  92 + delegateQueue:nil];
78 } 93 }
79 return self; 94 return self;
80 } 95 }
81 96
82 - (void)dealloc { 97 - (void)dealloc {
  98 + [self.session invalidateAndCancel];
  99 + self.session = nil;
  100 +
83 [self.downloadQueue cancelAllOperations]; 101 [self.downloadQueue cancelAllOperations];
84 SDDispatchQueueRelease(_barrierQueue); 102 SDDispatchQueueRelease(_barrierQueue);
85 } 103 }
@@ -120,7 +138,8 @@ @@ -120,7 +138,8 @@
120 __weak SDWebImageDownloader *wself = self; 138 __weak SDWebImageDownloader *wself = self;
121 139
122 return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{ 140 return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
123 - NSTimeInterval timeoutInterval = wself.downloadTimeout; 141 + __strong __typeof (wself) sself = wself;
  142 + NSTimeInterval timeoutInterval = sself.downloadTimeout;
124 if (timeoutInterval == 0.0) { 143 if (timeoutInterval == 0.0) {
125 timeoutInterval = 15.0; 144 timeoutInterval = 15.0;
126 } 145 }
@@ -129,19 +148,19 @@ @@ -129,19 +148,19 @@
129 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval]; 148 NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
130 request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies); 149 request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
131 request.HTTPShouldUsePipelining = YES; 150 request.HTTPShouldUsePipelining = YES;
132 - if (wself.headersFilter) {  
133 - request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]); 151 + if (sself.headersFilter) {
  152 + request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
134 } 153 }
135 else { 154 else {
136 - request.allHTTPHeaderFields = wself.HTTPHeaders; 155 + request.allHTTPHeaderFields = sself.HTTPHeaders;
137 } 156 }
138 - SDWebImageDownloaderOperation *operation = [[wself.operationClass alloc] initWithRequest:request options:options];  
139 - operation.shouldDecompressImages = wself.shouldDecompressImages; 157 + SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
  158 + operation.shouldDecompressImages = sself.shouldDecompressImages;
140 159
141 - if (wself.urlCredential) {  
142 - operation.credential = wself.urlCredential;  
143 - } else if (wself.username && wself.password) {  
144 - operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession]; 160 + if (sself.urlCredential) {
  161 + operation.credential = sself.urlCredential;
  162 + } else if (sself.username && sself.password) {
  163 + operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
145 } 164 }
146 165
147 if (options & SDWebImageDownloaderHighPriority) { 166 if (options & SDWebImageDownloaderHighPriority) {
@@ -150,11 +169,11 @@ @@ -150,11 +169,11 @@
150 operation.queuePriority = NSOperationQueuePriorityLow; 169 operation.queuePriority = NSOperationQueuePriorityLow;
151 } 170 }
152 171
153 - [wself.downloadQueue addOperation:operation];  
154 - if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) { 172 + [sself.downloadQueue addOperation:operation];
  173 + if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
155 // Emulate LIFO execution order by systematically adding new operations as last operation's dependency 174 // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
156 - [wself.lastAddedOperation addDependency:operation];  
157 - wself.lastAddedOperation = operation; 175 + [sself.lastAddedOperation addDependency:operation];
  176 + sself.lastAddedOperation = operation;
158 } 177 }
159 178
160 return operation; 179 return operation;
@@ -218,4 +237,66 @@ @@ -218,4 +237,66 @@
218 [self.downloadQueue cancelAllOperations]; 237 [self.downloadQueue cancelAllOperations];
219 } 238 }
220 239
  240 +#pragma mark Helper methods
  241 +
  242 +- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
  243 + SDWebImageDownloaderOperation *returnOperation = nil;
  244 + for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
  245 + if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
  246 + returnOperation = operation;
  247 + break;
  248 + }
  249 + }
  250 + return returnOperation;
  251 +}
  252 +
  253 +#pragma mark NSURLSessionDataDelegate
  254 +
  255 +- (void)URLSession:(NSURLSession *)session
  256 + dataTask:(NSURLSessionDataTask *)dataTask
  257 +didReceiveResponse:(NSURLResponse *)response
  258 + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  259 +
  260 + // Identify the operation that runs this task and pass it the delegate method
  261 + SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  262 +
  263 + [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
  264 +}
  265 +
  266 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  267 +
  268 + // Identify the operation that runs this task and pass it the delegate method
  269 + SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  270 +
  271 + [dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
  272 +}
  273 +
  274 +- (void)URLSession:(NSURLSession *)session
  275 + dataTask:(NSURLSessionDataTask *)dataTask
  276 + willCacheResponse:(NSCachedURLResponse *)proposedResponse
  277 + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  278 +
  279 + // Identify the operation that runs this task and pass it the delegate method
  280 + SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
  281 +
  282 + [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
  283 +}
  284 +
  285 +#pragma mark NSURLSessionTaskDelegate
  286 +
  287 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  288 + // Identify the operation that runs this task and pass it the delegate method
  289 + SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
  290 +
  291 + [dataOperation URLSession:session task:task didCompleteWithError:error];
  292 +}
  293 +
  294 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  295 +
  296 + // Identify the operation that runs this task and pass it the delegate method
  297 + SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
  298 +
  299 + [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
  300 +}
  301 +
221 @end 302 @end
@@ -15,22 +15,26 @@ extern NSString *const SDWebImageDownloadReceiveResponseNotification; @@ -15,22 +15,26 @@ extern NSString *const SDWebImageDownloadReceiveResponseNotification;
15 extern NSString *const SDWebImageDownloadStopNotification; 15 extern NSString *const SDWebImageDownloadStopNotification;
16 extern NSString *const SDWebImageDownloadFinishNotification; 16 extern NSString *const SDWebImageDownloadFinishNotification;
17 17
18 -@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation> 18 +@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
19 19
20 /** 20 /**
21 - * The request used by the operation's connection. 21 + * The request used by the operation's task.
22 */ 22 */
23 @property (strong, nonatomic, readonly) NSURLRequest *request; 23 @property (strong, nonatomic, readonly) NSURLRequest *request;
24 24
  25 +/**
  26 + * The operation's task
  27 + */
  28 +@property (strong, nonatomic, readonly) NSURLSessionTask *dataTask;
  29 +
25 30
26 @property (assign, nonatomic) BOOL shouldDecompressImages; 31 @property (assign, nonatomic) BOOL shouldDecompressImages;
27 32
28 /** 33 /**
29 - * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.  
30 - *  
31 - * This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. 34 + * Was used to determine whether the URL connection should consult the credential storage for authenticating the connection.
  35 + * @deprecated Not used for a couple of versions
32 */ 36 */
33 -@property (nonatomic, assign) BOOL shouldUseCredentialStorage; 37 +@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
34 38
35 /** 39 /**
36 * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. 40 * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
@@ -60,11 +64,13 @@ extern NSString *const SDWebImageDownloadFinishNotification; @@ -60,11 +64,13 @@ extern NSString *const SDWebImageDownloadFinishNotification;
60 * @see SDWebImageDownloaderOperation 64 * @see SDWebImageDownloaderOperation
61 * 65 *
62 * @param request the URL request 66 * @param request the URL request
  67 + * @param session the URL session in which this operation will run
63 * @param options downloader options 68 * @param options downloader options
64 * 69 *
65 * @return the initialized instance 70 * @return the initialized instance
66 */ 71 */
67 - (instancetype)initWithRequest:(NSURLRequest *)request 72 - (instancetype)initWithRequest:(NSURLRequest *)request
  73 + inSession:(NSURLSession *)session
68 options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER; 74 options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER;
69 75
70 /** 76 /**
@@ -20,14 +20,22 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis @@ -20,14 +20,22 @@ NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinis
20 static NSString *const kProgressCallbackKey = @"progress"; 20 static NSString *const kProgressCallbackKey = @"progress";
21 static NSString *const kCompletedCallbackKey = @"completed"; 21 static NSString *const kCompletedCallbackKey = @"completed";
22 22
23 -@interface SDWebImageDownloaderOperation () <NSURLConnectionDataDelegate> 23 +@interface SDWebImageDownloaderOperation ()
24 24
25 @property (strong, nonatomic) NSMutableArray *callbackBlocks; 25 @property (strong, nonatomic) NSMutableArray *callbackBlocks;
26 26
27 @property (assign, nonatomic, getter = isExecuting) BOOL executing; 27 @property (assign, nonatomic, getter = isExecuting) BOOL executing;
28 @property (assign, nonatomic, getter = isFinished) BOOL finished; 28 @property (assign, nonatomic, getter = isFinished) BOOL finished;
29 @property (strong, nonatomic) NSMutableData *imageData; 29 @property (strong, nonatomic) NSMutableData *imageData;
30 -@property (strong, nonatomic) NSURLConnection *connection; 30 +
  31 +// 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
  32 +// the task associated with this operation
  33 +@property (weak, nonatomic) NSURLSession *unownedSession;
  34 +// This is set if we're using not using an injected NSURLSession. We're responsible of invalidating this one
  35 +@property (strong, nonatomic) NSURLSession *ownedSession;
  36 +
  37 +@property (strong, nonatomic, readwrite) NSURLSessionTask *dataTask;
  38 +
31 @property (strong, atomic) NSThread *thread; 39 @property (strong, atomic) NSThread *thread;
32 @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue; 40 @property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
33 41
@@ -47,23 +55,22 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -47,23 +55,22 @@ static NSString *const kCompletedCallbackKey = @"completed";
47 @synthesize finished = _finished; 55 @synthesize finished = _finished;
48 56
49 - (instancetype)init { 57 - (instancetype)init {
50 - if (self = [self initWithRequest:nil options:0]) {  
51 - }  
52 - return self; 58 + return [self initWithRequest:nil inSession:nil options:0];
53 } 59 }
54 60
55 - (instancetype)initWithRequest:(NSURLRequest *)request 61 - (instancetype)initWithRequest:(NSURLRequest *)request
  62 + inSession:(NSURLSession *)session
56 options:(SDWebImageDownloaderOptions)options { 63 options:(SDWebImageDownloaderOptions)options {
57 if ((self = [super init])) { 64 if ((self = [super init])) {
58 _request = request; 65 _request = request;
59 _shouldDecompressImages = YES; 66 _shouldDecompressImages = YES;
60 - _shouldUseCredentialStorage = YES;  
61 _options = options; 67 _options = options;
62 _callbackBlocks = [NSMutableArray new]; 68 _callbackBlocks = [NSMutableArray new];
63 _executing = NO; 69 _executing = NO;
64 _finished = NO; 70 _finished = NO;
65 _expectedSize = 0; 71 _expectedSize = 0;
66 - responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called 72 + _unownedSession = session;
  73 + responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
67 _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT); 74 _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
68 } 75 }
69 return self; 76 return self;
@@ -134,38 +141,37 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -134,38 +141,37 @@ static NSString *const kCompletedCallbackKey = @"completed";
134 }]; 141 }];
135 } 142 }
136 #endif 143 #endif
137 - 144 + NSURLSession *session = self.unownedSession;
  145 + if (!self.unownedSession) {
  146 + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  147 + sessionConfig.timeoutIntervalForRequest = 15;
  148 +
  149 + /**
  150 + * Create the session for this task
  151 + * We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
  152 + * method calls and completion handler calls.
  153 + */
  154 + self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
  155 + delegate:self
  156 + delegateQueue:nil];
  157 + session = self.ownedSession;
  158 + }
  159 +
  160 + self.dataTask = [session dataTaskWithRequest:self.request];
138 self.executing = YES; 161 self.executing = YES;
139 - self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];  
140 self.thread = [NSThread currentThread]; 162 self.thread = [NSThread currentThread];
141 } 163 }
  164 +
  165 + [self.dataTask resume];
142 166
143 - [self.connection start];  
144 -  
145 - if (self.connection) { 167 + if (self.dataTask) {
146 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { 168 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
147 progressBlock(0, NSURLResponseUnknownLength); 169 progressBlock(0, NSURLResponseUnknownLength);
148 } 170 }
149 dispatch_async(dispatch_get_main_queue(), ^{ 171 dispatch_async(dispatch_get_main_queue(), ^{
150 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; 172 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
151 }); 173 });
152 -  
153 - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {  
154 - // Make sure to run the runloop in our background thread so it can process downloaded data  
155 - // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5  
156 - // not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)  
157 - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);  
158 - }  
159 - else {  
160 - CFRunLoopRun();  
161 - }  
162 -  
163 - if (!self.isFinished) {  
164 - [self.connection cancel];  
165 - [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];  
166 - }  
167 - }  
168 - else { 174 + } else {
169 for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) { 175 for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
170 completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES); 176 completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
171 } 177 }
@@ -198,15 +204,14 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -198,15 +204,14 @@ static NSString *const kCompletedCallbackKey = @"completed";
198 - (void)cancelInternalAndStop { 204 - (void)cancelInternalAndStop {
199 if (self.isFinished) return; 205 if (self.isFinished) return;
200 [self cancelInternal]; 206 [self cancelInternal];
201 - CFRunLoopStop(CFRunLoopGetCurrent());  
202 } 207 }
203 208
204 - (void)cancelInternal { 209 - (void)cancelInternal {
205 if (self.isFinished) return; 210 if (self.isFinished) return;
206 [super cancel]; 211 [super cancel];
207 212
208 - if (self.connection) {  
209 - [self.connection cancel]; 213 + if (self.dataTask) {
  214 + [self.dataTask cancel];
210 dispatch_async(dispatch_get_main_queue(), ^{ 215 dispatch_async(dispatch_get_main_queue(), ^{
211 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; 216 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
212 }); 217 });
@@ -230,9 +235,13 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -230,9 +235,13 @@ static NSString *const kCompletedCallbackKey = @"completed";
230 dispatch_barrier_async(self.barrierQueue, ^{ 235 dispatch_barrier_async(self.barrierQueue, ^{
231 [self.callbackBlocks removeAllObjects]; 236 [self.callbackBlocks removeAllObjects];
232 }); 237 });
233 - self.connection = nil; 238 + self.dataTask = nil;
234 self.imageData = nil; 239 self.imageData = nil;
235 self.thread = nil; 240 self.thread = nil;
  241 + if (self.ownedSession) {
  242 + [self.ownedSession invalidateAndCancel];
  243 + self.ownedSession = nil;
  244 + }
236 } 245 }
237 246
238 - (void)setFinished:(BOOL)finished { 247 - (void)setFinished:(BOOL)finished {
@@ -251,9 +260,12 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -251,9 +260,12 @@ static NSString *const kCompletedCallbackKey = @"completed";
251 return YES; 260 return YES;
252 } 261 }
253 262
254 -#pragma mark NSURLConnection (delegate) 263 +#pragma mark NSURLSessionDataDelegate
255 264
256 -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 265 +- (void)URLSession:(NSURLSession *)session
  266 + dataTask:(NSURLSessionDataTask *)dataTask
  267 +didReceiveResponse:(NSURLResponse *)response
  268 + completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
257 269
258 //'304 Not Modified' is an exceptional one 270 //'304 Not Modified' is an exceptional one
259 if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) { 271 if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
@@ -262,7 +274,7 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -262,7 +274,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
262 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { 274 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
263 progressBlock(0, expected); 275 progressBlock(0, expected);
264 } 276 }
265 - 277 +
266 self.imageData = [[NSMutableData alloc] initWithCapacity:expected]; 278 self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
267 self.response = response; 279 self.response = response;
268 dispatch_async(dispatch_get_main_queue(), ^{ 280 dispatch_async(dispatch_get_main_queue(), ^{
@@ -277,7 +289,7 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -277,7 +289,7 @@ static NSString *const kCompletedCallbackKey = @"completed";
277 if (code == 304) { 289 if (code == 304) {
278 [self cancelInternal]; 290 [self cancelInternal];
279 } else { 291 } else {
280 - [self.connection cancel]; 292 + [self.dataTask cancel];
281 } 293 }
282 dispatch_async(dispatch_get_main_queue(), ^{ 294 dispatch_async(dispatch_get_main_queue(), ^{
283 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; 295 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
@@ -286,12 +298,15 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -286,12 +298,15 @@ static NSString *const kCompletedCallbackKey = @"completed";
286 for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) { 298 for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {
287 completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil], YES); 299 completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil], YES);
288 } 300 }
289 - CFRunLoopStop(CFRunLoopGetCurrent());  
290 [self done]; 301 [self done];
291 } 302 }
  303 +
  304 + if (completionHandler) {
  305 + completionHandler(NSURLSessionResponseAllow);
  306 + }
292 } 307 }
293 308
294 -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 309 +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
295 [self.imageData appendData:data]; 310 [self.imageData appendData:data];
296 311
297 if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) { 312 if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
@@ -319,10 +334,9 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -319,10 +334,9 @@ static NSString *const kCompletedCallbackKey = @"completed";
319 // When we draw to Core Graphics, we lose orientation information, 334 // When we draw to Core Graphics, we lose orientation information,
320 // which means the image below born of initWithCGIImage will be 335 // which means the image below born of initWithCGIImage will be
321 // oriented incorrectly sometimes. (Unlike the image born of initWithData 336 // oriented incorrectly sometimes. (Unlike the image born of initWithData
322 - // in connectionDidFinishLoading.) So save it here and pass it on later. 337 + // in didCompleteWithError.) So save it here and pass it on later.
323 orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)]; 338 orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
324 } 339 }
325 -  
326 } 340 }
327 341
328 if (width + height > 0 && totalSize < self.expectedSize) { 342 if (width + height > 0 && totalSize < self.expectedSize) {
@@ -376,138 +390,143 @@ static NSString *const kCompletedCallbackKey = @"completed"; @@ -376,138 +390,143 @@ static NSString *const kCompletedCallbackKey = @"completed";
376 } 390 }
377 } 391 }
378 392
379 -+ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {  
380 - switch (value) {  
381 - case 1:  
382 - return UIImageOrientationUp;  
383 - case 3:  
384 - return UIImageOrientationDown;  
385 - case 8:  
386 - return UIImageOrientationLeft;  
387 - case 6:  
388 - return UIImageOrientationRight;  
389 - case 2:  
390 - return UIImageOrientationUpMirrored;  
391 - case 4:  
392 - return UIImageOrientationDownMirrored;  
393 - case 5:  
394 - return UIImageOrientationLeftMirrored;  
395 - case 7:  
396 - return UIImageOrientationRightMirrored;  
397 - default:  
398 - return UIImageOrientationUp; 393 +- (void)URLSession:(NSURLSession *)session
  394 + dataTask:(NSURLSessionDataTask *)dataTask
  395 + willCacheResponse:(NSCachedURLResponse *)proposedResponse
  396 + completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  397 +
  398 + responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
  399 + NSCachedURLResponse *cachedResponse = proposedResponse;
  400 +
  401 + if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
  402 + // Prevents caching of responses
  403 + cachedResponse = nil;
  404 + }
  405 + if (completionHandler) {
  406 + completionHandler(cachedResponse);
399 } 407 }
400 } 408 }
401 409
402 -- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {  
403 - return SDScaledImageForKey(key, image);  
404 -} 410 +#pragma mark NSURLSessionTaskDelegate
405 411
406 -- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { 412 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
407 NSArray *completionBlocks = [[self callbacksForKey:kCompletedCallbackKey] copy]; 413 NSArray *completionBlocks = [[self callbacksForKey:kCompletedCallbackKey] copy];
408 @synchronized(self) { 414 @synchronized(self) {
409 - CFRunLoopStop(CFRunLoopGetCurrent());  
410 self.thread = nil; 415 self.thread = nil;
411 - self.connection = nil; 416 + self.dataTask = nil;
412 dispatch_async(dispatch_get_main_queue(), ^{ 417 dispatch_async(dispatch_get_main_queue(), ^{
413 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self]; 418 [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
414 - [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self]; 419 + if (!error) {
  420 + [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
  421 + }
415 }); 422 });
416 } 423 }
417 424
418 - if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {  
419 - responseFromCached = NO;  
420 - }  
421 -  
422 - if (completionBlocks.count > 0) {  
423 - if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {  
424 - for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {  
425 - completionBlock(nil, nil, nil, YES);  
426 - }  
427 - } else if (self.imageData) {  
428 - UIImage *image = [UIImage sd_imageWithData:self.imageData];  
429 - NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];  
430 - image = [self scaledImageForKey:key image:image];  
431 -  
432 - // Do not force decoding animated GIFs  
433 - if (!image.images) {  
434 - if (self.shouldDecompressImages) {  
435 - image = [UIImage decodedImageWithImage:image]; 425 + if (error) {
  426 + for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
  427 + completionBlock(nil, nil, error, YES);
  428 + }
  429 + } else {
  430 + if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
  431 + responseFromCached = NO;
  432 + }
  433 +
  434 + if (completionBlocks.count > 0) {
  435 + if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
  436 + for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
  437 + completionBlock(nil, nil, nil, YES);
  438 + }
  439 + } else if (self.imageData) {
  440 + UIImage *image = [UIImage sd_imageWithData:self.imageData];
  441 + NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
  442 + image = [self scaledImageForKey:key image:image];
  443 +
  444 + // Do not force decoding animated GIFs
  445 + if (!image.images) {
  446 + if (self.shouldDecompressImages) {
  447 + image = [UIImage decodedImageWithImage:image];
  448 + }
436 } 449 }
437 - }  
438 - for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {  
439 if (CGSizeEqualToSize(image.size, CGSizeZero)) { 450 if (CGSizeEqualToSize(image.size, CGSizeZero)) {
440 - completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES); 451 + for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
  452 + completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
  453 + }
  454 + } else {
  455 + for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
  456 + completionBlock(image, self.imageData, nil, YES);
  457 + }
441 } 458 }
442 - else {  
443 - completionBlock(image, self.imageData, nil, YES); 459 + } else {
  460 + for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {
  461 + completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
444 } 462 }
445 } 463 }
446 - } else {  
447 - for (SDWebImageDownloaderCompletedBlock completionBlock in completionBlocks) {  
448 - completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);  
449 - }  
450 } 464 }
451 } 465 }
452 [self done]; 466 [self done];
453 } 467 }
454 468
455 -- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {  
456 - @synchronized(self) {  
457 - CFRunLoopStop(CFRunLoopGetCurrent());  
458 - self.thread = nil;  
459 - self.connection = nil;  
460 - dispatch_async(dispatch_get_main_queue(), ^{  
461 - [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];  
462 - });  
463 - }  
464 -  
465 - for (SDWebImageDownloaderCompletedBlock completedBlock in [self callbacksForKey:kCompletedCallbackKey]) {  
466 - completedBlock(nil, nil, error, YES);  
467 - }  
468 - self.completionBlock = nil;  
469 - [self done];  
470 -}  
471 -  
472 -- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {  
473 - responseFromCached = NO; // If this method is called, it means the response wasn't read from cache  
474 - if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {  
475 - // Prevents caching of responses  
476 - return nil;  
477 - }  
478 - else {  
479 - return cachedResponse;  
480 - }  
481 -}  
482 -  
483 -- (BOOL)shouldContinueWhenAppEntersBackground {  
484 - return self.options & SDWebImageDownloaderContinueInBackground;  
485 -}  
486 -  
487 -- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {  
488 - return self.shouldUseCredentialStorage;  
489 -}  
490 -  
491 -- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ 469 +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
  470 +
  471 + NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
  472 + __block NSURLCredential *credential = nil;
  473 +
492 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 474 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
493 - if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates) &&  
494 - [challenge.sender respondsToSelector:@selector(performDefaultHandlingForAuthenticationChallenge:)]) {  
495 - [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge]; 475 + if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
  476 + disposition = NSURLSessionAuthChallengePerformDefaultHandling;
496 } else { 477 } else {
497 - NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];  
498 - [challenge.sender useCredential:credential forAuthenticationChallenge:challenge]; 478 + credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
  479 + disposition = NSURLSessionAuthChallengeUseCredential;
499 } 480 }
500 } else { 481 } else {
501 if (challenge.previousFailureCount == 0) { 482 if (challenge.previousFailureCount == 0) {
502 if (self.credential) { 483 if (self.credential) {
503 - [challenge.sender useCredential:self.credential forAuthenticationChallenge:challenge]; 484 + credential = self.credential;
  485 + disposition = NSURLSessionAuthChallengeUseCredential;
504 } else { 486 } else {
505 - [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; 487 + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
506 } 488 }
507 } else { 489 } else {
508 - [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; 490 + disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
509 } 491 }
510 } 492 }
  493 +
  494 + if (completionHandler) {
  495 + completionHandler(disposition, credential);
  496 + }
  497 +}
  498 +
  499 +#pragma mark Helper methods
  500 +
  501 ++ (UIImageOrientation)orientationFromPropertyValue:(NSInteger)value {
  502 + switch (value) {
  503 + case 1:
  504 + return UIImageOrientationUp;
  505 + case 3:
  506 + return UIImageOrientationDown;
  507 + case 8:
  508 + return UIImageOrientationLeft;
  509 + case 6:
  510 + return UIImageOrientationRight;
  511 + case 2:
  512 + return UIImageOrientationUpMirrored;
  513 + case 4:
  514 + return UIImageOrientationDownMirrored;
  515 + case 5:
  516 + return UIImageOrientationLeftMirrored;
  517 + case 7:
  518 + return UIImageOrientationRightMirrored;
  519 + default:
  520 + return UIImageOrientationUp;
  521 + }
  522 +}
  523 +
  524 +- (UIImage *)scaledImageForKey:(NSString *)key image:(UIImage *)image {
  525 + return SDScaledImageForKey(key, image);
  526 +}
  527 +
  528 +- (BOOL)shouldContinueWhenAppEntersBackground {
  529 + return self.options & SDWebImageDownloaderContinueInBackground;
511 } 530 }
512 531
513 @end 532 @end
@@ -54,14 +54,17 @@ @@ -54,14 +54,17 @@
54 } 54 }
55 55
56 - (NSString *)cacheKeyForURL:(NSURL *)url { 56 - (NSString *)cacheKeyForURL:(NSURL *)url {
  57 + if (!url) {
  58 + return @"";
  59 + }
  60 +
57 if (self.cacheKeyFilter) { 61 if (self.cacheKeyFilter) {
58 return self.cacheKeyFilter(url); 62 return self.cacheKeyFilter(url);
59 - }  
60 - else { 63 + } else {
61 if (NSClassFromString(@"NSURLComponents") && [NSURLComponents instancesRespondToSelector:@selector(string)]) { 64 if (NSClassFromString(@"NSURLComponents") && [NSURLComponents instancesRespondToSelector:@selector(string)]) {
62 NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO]; 65 NSURLComponents *urlComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
63 urlComponents.query = nil; // Strip out query parameters. 66 urlComponents.query = nil; // Strip out query parameters.
64 - return urlComponents.string; 67 + return urlComponents.URL.absoluteString;
65 } else { 68 } else {
66 return url.absoluteString; 69 return url.absoluteString;
67 } 70 }
@@ -4,7 +4,7 @@ xcodeproj 'SDWebImage Tests' @@ -4,7 +4,7 @@ xcodeproj 'SDWebImage Tests'
4 workspace '../SDWebImage' 4 workspace '../SDWebImage'
5 5
6 target 'Tests' do 6 target 'Tests' do
7 - platform :ios, '5.0' 7 + platform :ios, '7.0'
8 pod 'Expecta', '<=0.3.1' 8 pod 'Expecta', '<=0.3.1'
9 pod 'SDWebImage', :path => '../' 9 pod 'SDWebImage', :path => '../'
10 10
@@ -8,8 +8,8 @@ @@ -8,8 +8,8 @@
8 8
9 /* Begin PBXBuildFile section */ 9 /* Begin PBXBuildFile section */
10 0D87E1F83BD319CEC7622E9F /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0462A7F023A057322E59B3C5 /* libPods-Tests.a */; }; 10 0D87E1F83BD319CEC7622E9F /* libPods-Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0462A7F023A057322E59B3C5 /* libPods-Tests.a */; };
11 - 5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */; };  
12 1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; }; 11 1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
  12 + 5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */; };
13 DA248D57195472AA00390AB0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D56195472AA00390AB0 /* XCTest.framework */; }; 13 DA248D57195472AA00390AB0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D56195472AA00390AB0 /* XCTest.framework */; };
14 DA248D59195472AA00390AB0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D58195472AA00390AB0 /* Foundation.framework */; }; 14 DA248D59195472AA00390AB0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D58195472AA00390AB0 /* Foundation.framework */; };
15 DA248D5B195472AA00390AB0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D5A195472AA00390AB0 /* UIKit.framework */; }; 15 DA248D5B195472AA00390AB0 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA248D5A195472AA00390AB0 /* UIKit.framework */; };
@@ -21,10 +21,10 @@ @@ -21,10 +21,10 @@
21 21
22 /* Begin PBXFileReference section */ 22 /* Begin PBXFileReference section */
23 0462A7F023A057322E59B3C5 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 23 0462A7F023A057322E59B3C5 /* libPods-Tests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Tests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
  24 + 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = "<group>"; };
24 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = TestImage.jpg; sourceTree = "<group>"; }; 25 5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = TestImage.jpg; sourceTree = "<group>"; };
25 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>"; }; 26 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>"; };
26 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>"; }; 27 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>"; };
27 - 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = "<group>"; };  
28 DA248D53195472AA00390AB0 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 28 DA248D53195472AA00390AB0 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
29 DA248D56195472AA00390AB0 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 29 DA248D56195472AA00390AB0 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
30 DA248D58195472AA00390AB0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 30 DA248D58195472AA00390AB0 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
@@ -251,7 +251,7 @@ @@ -251,7 +251,7 @@
251 isa = XCBuildConfiguration; 251 isa = XCBuildConfiguration;
252 buildSettings = { 252 buildSettings = {
253 ENABLE_TESTABILITY = YES; 253 ENABLE_TESTABILITY = YES;
254 - IPHONEOS_DEPLOYMENT_TARGET = 6.0; 254 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
255 ONLY_ACTIVE_ARCH = YES; 255 ONLY_ACTIVE_ARCH = YES;
256 }; 256 };
257 name = Debug; 257 name = Debug;
@@ -259,7 +259,7 @@ @@ -259,7 +259,7 @@
259 DA248D4B1954721A00390AB0 /* Release */ = { 259 DA248D4B1954721A00390AB0 /* Release */ = {
260 isa = XCBuildConfiguration; 260 isa = XCBuildConfiguration;
261 buildSettings = { 261 buildSettings = {
262 - IPHONEOS_DEPLOYMENT_TARGET = 6.0; 262 + IPHONEOS_DEPLOYMENT_TARGET = 7.0;
263 }; 263 };
264 name = Release; 264 name = Release;
265 }; 265 };
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 <key>CFBundlePackageType</key> 15 <key>CFBundlePackageType</key>
16 <string>FMWK</string> 16 <string>FMWK</string>
17 <key>CFBundleShortVersionString</key> 17 <key>CFBundleShortVersionString</key>
18 - <string>3.7.6</string> 18 + <string>3.8.0</string>
19 <key>CFBundleSignature</key> 19 <key>CFBundleSignature</key>
20 <string>????</string> 20 <string>????</string>
21 <key>CFBundleVersion</key> 21 <key>CFBundleVersion</key>