1275 lines
56 KiB
Objective-C
1275 lines
56 KiB
Objective-C
// AFURLSessionManager.m
|
||
// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ )
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
// of this software and associated documentation files (the "Software"), to deal
|
||
// in the Software without restriction, including without limitation the rights
|
||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the Software is
|
||
// furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included in
|
||
// all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
// THE SOFTWARE.
|
||
|
||
#import "AFURLSessionManager.h"
|
||
#import <objc/runtime.h>
|
||
|
||
static dispatch_queue_t url_session_manager_processing_queue() {
|
||
static dispatch_queue_t af_url_session_manager_processing_queue;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
|
||
});
|
||
|
||
return af_url_session_manager_processing_queue;
|
||
}
|
||
|
||
static dispatch_group_t url_session_manager_completion_group() {
|
||
static dispatch_group_t af_url_session_manager_completion_group;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
af_url_session_manager_completion_group = dispatch_group_create();
|
||
});
|
||
|
||
return af_url_session_manager_completion_group;
|
||
}
|
||
|
||
NSString * const AFNetworkingTaskDidResumeNotification = @"com.alamofire.networking.task.resume";
|
||
NSString * const AFNetworkingTaskDidCompleteNotification = @"com.alamofire.networking.task.complete";
|
||
NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networking.task.suspend";
|
||
NSString * const AFURLSessionDidInvalidateNotification = @"com.alamofire.networking.session.invalidate";
|
||
NSString * const AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification = @"com.alamofire.networking.session.download.file-manager-succeed";
|
||
NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification = @"com.alamofire.networking.session.download.file-manager-error";
|
||
|
||
NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse";
|
||
NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer";
|
||
NSString * const AFNetworkingTaskDidCompleteResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata";
|
||
NSString * const AFNetworkingTaskDidCompleteErrorKey = @"com.alamofire.networking.task.complete.error";
|
||
NSString * const AFNetworkingTaskDidCompleteAssetPathKey = @"com.alamofire.networking.task.complete.assetpath";
|
||
NSString * const AFNetworkingTaskDidCompleteSessionTaskMetrics = @"com.alamofire.networking.complete.sessiontaskmetrics";
|
||
|
||
static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock";
|
||
|
||
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
|
||
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
|
||
|
||
typedef NSURLRequest * (^AFURLSessionTaskWillPerformHTTPRedirectionBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request);
|
||
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionTaskDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
|
||
typedef id (^AFURLSessionTaskAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential));
|
||
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);
|
||
|
||
typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task);
|
||
typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
|
||
typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error);
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
typedef void (^AFURLSessionTaskDidFinishCollectingMetricsBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * metrics) AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
|
||
#endif
|
||
|
||
typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response);
|
||
typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
|
||
typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
|
||
typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse);
|
||
|
||
typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
|
||
typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
|
||
typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes);
|
||
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);
|
||
|
||
typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);
|
||
|
||
#pragma mark -
|
||
|
||
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
|
||
- (instancetype)initWithTask:(NSURLSessionTask *)task;
|
||
@property (nonatomic, weak) AFURLSessionManager *manager;
|
||
@property (nonatomic, strong) NSMutableData *mutableData;
|
||
@property (nonatomic, strong) NSProgress *uploadProgress;
|
||
@property (nonatomic, strong) NSProgress *downloadProgress;
|
||
@property (nonatomic, copy) NSURL *downloadFileURL;
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
|
||
#endif
|
||
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
|
||
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
|
||
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
|
||
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
|
||
@end
|
||
|
||
@implementation AFURLSessionManagerTaskDelegate
|
||
|
||
- (instancetype)initWithTask:(NSURLSessionTask *)task {
|
||
self = [super init];
|
||
if (!self) {
|
||
return nil;
|
||
}
|
||
|
||
_mutableData = [NSMutableData data];
|
||
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
||
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
|
||
|
||
__weak __typeof__(task) weakTask = task;
|
||
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
|
||
{
|
||
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
|
||
progress.cancellable = YES;
|
||
progress.cancellationHandler = ^{
|
||
[weakTask cancel];
|
||
};
|
||
progress.pausable = YES;
|
||
progress.pausingHandler = ^{
|
||
[weakTask suspend];
|
||
};
|
||
#if AF_CAN_USE_AT_AVAILABLE
|
||
if (@available(macOS 10.11, *))
|
||
#else
|
||
if ([progress respondsToSelector:@selector(setResumingHandler:)])
|
||
#endif
|
||
{
|
||
progress.resumingHandler = ^{
|
||
[weakTask resume];
|
||
};
|
||
}
|
||
|
||
[progress addObserver:self
|
||
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
|
||
options:NSKeyValueObservingOptionNew
|
||
context:NULL];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc {
|
||
[self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
|
||
[self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
|
||
}
|
||
|
||
#pragma mark - NSProgress Tracking
|
||
|
||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
|
||
if ([object isEqual:self.downloadProgress]) {
|
||
if (self.downloadProgressBlock) {
|
||
self.downloadProgressBlock(object);
|
||
}
|
||
}
|
||
else if ([object isEqual:self.uploadProgress]) {
|
||
if (self.uploadProgressBlock) {
|
||
self.uploadProgressBlock(object);
|
||
}
|
||
}
|
||
}
|
||
|
||
static const void * const AuthenticationChallengeErrorKey = &AuthenticationChallengeErrorKey;
|
||
|
||
#pragma mark - NSURLSessionTaskDelegate
|
||
|
||
- (void)URLSession:(__unused NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didCompleteWithError:(NSError *)error
|
||
{
|
||
error = objc_getAssociatedObject(task, AuthenticationChallengeErrorKey) ?: error;
|
||
__strong AFURLSessionManager *manager = self.manager;
|
||
|
||
__block id responseObject = nil;
|
||
|
||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
|
||
|
||
//Performance Improvement from #2672
|
||
NSData *data = nil;
|
||
if (self.mutableData) {
|
||
data = [self.mutableData copy];
|
||
//We no longer need the reference, so nil it out to gain back some memory.
|
||
self.mutableData = nil;
|
||
}
|
||
|
||
#if AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
|
||
if (self.sessionTaskMetrics) {
|
||
userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
if (self.downloadFileURL) {
|
||
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
|
||
} else if (data) {
|
||
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
|
||
}
|
||
|
||
if (error) {
|
||
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
|
||
|
||
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
|
||
if (self.completionHandler) {
|
||
self.completionHandler(task.response, responseObject, error);
|
||
}
|
||
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
|
||
});
|
||
});
|
||
} else {
|
||
dispatch_async(url_session_manager_processing_queue(), ^{
|
||
NSError *serializationError = nil;
|
||
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
|
||
|
||
if (self.downloadFileURL) {
|
||
responseObject = self.downloadFileURL;
|
||
}
|
||
|
||
if (responseObject) {
|
||
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
|
||
}
|
||
|
||
if (serializationError) {
|
||
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
|
||
}
|
||
|
||
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
|
||
if (self.completionHandler) {
|
||
self.completionHandler(task.response, responseObject, serializationError);
|
||
}
|
||
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
|
||
});
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10)) {
|
||
self.sessionTaskMetrics = metrics;
|
||
}
|
||
#endif
|
||
|
||
#pragma mark - NSURLSessionDataDelegate
|
||
|
||
- (void)URLSession:(__unused NSURLSession *)session
|
||
dataTask:(__unused NSURLSessionDataTask *)dataTask
|
||
didReceiveData:(NSData *)data
|
||
{
|
||
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
|
||
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
|
||
|
||
[self.mutableData appendData:data];
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
|
||
didSendBodyData:(int64_t)bytesSent
|
||
totalBytesSent:(int64_t)totalBytesSent
|
||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
|
||
|
||
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
|
||
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
|
||
}
|
||
|
||
#pragma mark - NSURLSessionDownloadDelegate
|
||
|
||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didWriteData:(int64_t)bytesWritten
|
||
totalBytesWritten:(int64_t)totalBytesWritten
|
||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
|
||
|
||
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
|
||
self.downloadProgress.completedUnitCount = totalBytesWritten;
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didResumeAtOffset:(int64_t)fileOffset
|
||
expectedTotalBytes:(int64_t)expectedTotalBytes{
|
||
|
||
self.downloadProgress.totalUnitCount = expectedTotalBytes;
|
||
self.downloadProgress.completedUnitCount = fileOffset;
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didFinishDownloadingToURL:(NSURL *)location
|
||
{
|
||
self.downloadFileURL = nil;
|
||
|
||
if (self.downloadTaskDidFinishDownloading) {
|
||
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
|
||
if (self.downloadFileURL) {
|
||
NSError *fileManagerError = nil;
|
||
|
||
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
|
||
} else {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
#pragma mark -
|
||
|
||
/**
|
||
* A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`.
|
||
*
|
||
* See:
|
||
* - https://github.com/AFNetworking/AFNetworking/issues/1477
|
||
* - https://github.com/AFNetworking/AFNetworking/issues/2638
|
||
* - https://github.com/AFNetworking/AFNetworking/pull/2702
|
||
*/
|
||
|
||
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
|
||
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
|
||
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
|
||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||
}
|
||
|
||
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
|
||
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
|
||
}
|
||
|
||
static NSString * const AFNSURLSessionTaskDidResumeNotification = @"com.alamofire.networking.nsurlsessiontask.resume";
|
||
static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofire.networking.nsurlsessiontask.suspend";
|
||
|
||
@interface _AFURLSessionTaskSwizzling : NSObject
|
||
|
||
@end
|
||
|
||
@implementation _AFURLSessionTaskSwizzling
|
||
|
||
+ (void)load {
|
||
/**
|
||
WARNING: Trouble Ahead
|
||
https://github.com/AFNetworking/AFNetworking/pull/2702
|
||
*/
|
||
|
||
if (NSClassFromString(@"NSURLSessionTask")) {
|
||
/**
|
||
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
|
||
Many Unit Tests have been built to validate as much of this behavior has possible.
|
||
Here is what we know:
|
||
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
|
||
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
|
||
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
|
||
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
|
||
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
|
||
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
|
||
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
|
||
|
||
Some Assumptions:
|
||
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
|
||
- No background task classes override `resume` or `suspend`
|
||
|
||
The current solution:
|
||
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
|
||
2) Grab a pointer to the original implementation of `af_resume`
|
||
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
|
||
4) Grab the super class of the current class.
|
||
5) Grab a pointer for the current class to the current implementation of `resume`.
|
||
6) Grab a pointer for the super class to the current implementation of `resume`.
|
||
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
|
||
8) Set the current class to the super class, and repeat steps 3-8
|
||
*/
|
||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wnonnull"
|
||
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
|
||
#pragma clang diagnostic pop
|
||
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
|
||
Class currentClass = [localDataTask class];
|
||
|
||
while (class_getInstanceMethod(currentClass, @selector(resume))) {
|
||
Class superClass = [currentClass superclass];
|
||
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
|
||
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
|
||
if (classResumeIMP != superclassResumeIMP &&
|
||
originalAFResumeIMP != classResumeIMP) {
|
||
[self swizzleResumeAndSuspendMethodForClass:currentClass];
|
||
}
|
||
currentClass = [currentClass superclass];
|
||
}
|
||
|
||
[localDataTask cancel];
|
||
[session finishTasksAndInvalidate];
|
||
}
|
||
}
|
||
|
||
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
|
||
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
|
||
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
|
||
|
||
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
|
||
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
|
||
}
|
||
|
||
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
|
||
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
|
||
}
|
||
}
|
||
|
||
- (NSURLSessionTaskState)state {
|
||
NSAssert(NO, @"State method should never be called in the actual dummy class");
|
||
return NSURLSessionTaskStateCanceling;
|
||
}
|
||
|
||
- (void)af_resume {
|
||
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
|
||
NSURLSessionTaskState state = [self state];
|
||
[self af_resume];
|
||
|
||
if (state != NSURLSessionTaskStateRunning) {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
|
||
}
|
||
}
|
||
|
||
- (void)af_suspend {
|
||
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
|
||
NSURLSessionTaskState state = [self state];
|
||
[self af_suspend];
|
||
|
||
if (state != NSURLSessionTaskStateSuspended) {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
|
||
}
|
||
}
|
||
@end
|
||
|
||
#pragma mark -
|
||
|
||
@interface AFURLSessionManager ()
|
||
@property (readwrite, nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
|
||
@property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue;
|
||
@property (readwrite, nonatomic, strong) NSURLSession *session;
|
||
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableTaskDelegatesKeyedByTaskIdentifier;
|
||
@property (readonly, nonatomic, copy) NSString *taskDescriptionForSessionTasks;
|
||
@property (readwrite, nonatomic, strong) NSLock *lock;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession AF_API_UNAVAILABLE(macos);
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskAuthenticationChallengeBlock authenticationChallengeHandler;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidFinishCollectingMetricsBlock taskDidFinishCollectingMetrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10));
|
||
#endif
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
|
||
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
|
||
@end
|
||
|
||
@implementation AFURLSessionManager
|
||
|
||
- (instancetype)init {
|
||
return [self initWithSessionConfiguration:nil];
|
||
}
|
||
|
||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
|
||
self = [super init];
|
||
if (!self) {
|
||
return nil;
|
||
}
|
||
|
||
if (!configuration) {
|
||
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||
}
|
||
|
||
self.sessionConfiguration = configuration;
|
||
|
||
self.operationQueue = [[NSOperationQueue alloc] init];
|
||
self.operationQueue.maxConcurrentOperationCount = 1;
|
||
|
||
self.responseSerializer = [AFJSONResponseSerializer serializer];
|
||
|
||
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
|
||
|
||
#if !TARGET_OS_WATCH
|
||
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
||
#endif
|
||
|
||
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
|
||
|
||
self.lock = [[NSLock alloc] init];
|
||
self.lock.name = AFURLSessionManagerLockName;
|
||
|
||
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
||
for (NSURLSessionDataTask *task in dataTasks) {
|
||
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
|
||
}
|
||
|
||
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
|
||
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
|
||
}
|
||
|
||
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
|
||
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
|
||
}
|
||
}];
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc {
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (NSURLSession *)session {
|
||
|
||
@synchronized (self) {
|
||
if (!_session) {
|
||
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
|
||
}
|
||
}
|
||
return _session;
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
|
||
- (NSString *)taskDescriptionForSessionTasks {
|
||
return [NSString stringWithFormat:@"%p", self];
|
||
}
|
||
|
||
- (void)taskDidResume:(NSNotification *)notification {
|
||
NSURLSessionTask *task = notification.object;
|
||
if ([task respondsToSelector:@selector(taskDescription)]) {
|
||
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void)taskDidSuspend:(NSNotification *)notification {
|
||
NSURLSessionTask *task = notification.object;
|
||
if ([task respondsToSelector:@selector(taskDescription)]) {
|
||
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
|
||
NSParameterAssert(task);
|
||
|
||
AFURLSessionManagerTaskDelegate *delegate = nil;
|
||
[self.lock lock];
|
||
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
|
||
[self.lock unlock];
|
||
|
||
return delegate;
|
||
}
|
||
|
||
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
|
||
forTask:(NSURLSessionTask *)task
|
||
{
|
||
NSParameterAssert(task);
|
||
NSParameterAssert(delegate);
|
||
|
||
[self.lock lock];
|
||
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
|
||
[self addNotificationObserverForTask:task];
|
||
[self.lock unlock];
|
||
}
|
||
|
||
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
|
||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
|
||
delegate.manager = self;
|
||
delegate.completionHandler = completionHandler;
|
||
|
||
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||
[self setDelegate:delegate forTask:dataTask];
|
||
|
||
delegate.uploadProgressBlock = uploadProgressBlock;
|
||
delegate.downloadProgressBlock = downloadProgressBlock;
|
||
}
|
||
|
||
- (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask
|
||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:uploadTask];
|
||
delegate.manager = self;
|
||
delegate.completionHandler = completionHandler;
|
||
|
||
uploadTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||
|
||
[self setDelegate:delegate forTask:uploadTask];
|
||
|
||
delegate.uploadProgressBlock = uploadProgressBlock;
|
||
}
|
||
|
||
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
|
||
delegate.manager = self;
|
||
delegate.completionHandler = completionHandler;
|
||
|
||
if (destination) {
|
||
delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
|
||
return destination(location, task.response);
|
||
};
|
||
}
|
||
|
||
downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
|
||
|
||
[self setDelegate:delegate forTask:downloadTask];
|
||
|
||
delegate.downloadProgressBlock = downloadProgressBlock;
|
||
}
|
||
|
||
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
|
||
NSParameterAssert(task);
|
||
|
||
[self.lock lock];
|
||
[self removeNotificationObserverForTask:task];
|
||
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
|
||
[self.lock unlock];
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
|
||
__block NSArray *tasks = nil;
|
||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
|
||
tasks = dataTasks;
|
||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
|
||
tasks = uploadTasks;
|
||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
|
||
tasks = downloadTasks;
|
||
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
|
||
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
|
||
}
|
||
|
||
dispatch_semaphore_signal(semaphore);
|
||
}];
|
||
|
||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||
|
||
return tasks;
|
||
}
|
||
|
||
- (NSArray *)tasks {
|
||
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
- (NSArray *)dataTasks {
|
||
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
- (NSArray *)uploadTasks {
|
||
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
- (NSArray *)downloadTasks {
|
||
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks resetSession:(BOOL)resetSession {
|
||
if (cancelPendingTasks) {
|
||
[self.session invalidateAndCancel];
|
||
} else {
|
||
[self.session finishTasksAndInvalidate];
|
||
}
|
||
if (resetSession) {
|
||
self.session = nil;
|
||
}
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (void)setResponseSerializer:(id <AFURLResponseSerialization>)responseSerializer {
|
||
NSParameterAssert(responseSerializer);
|
||
|
||
_responseSerializer = responseSerializer;
|
||
}
|
||
|
||
#pragma mark -
|
||
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
|
||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
|
||
}
|
||
|
||
- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task {
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task];
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task];
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
|
||
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
|
||
|
||
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
|
||
|
||
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
|
||
|
||
return dataTask;
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||
fromFile:(NSURL *)fileURL
|
||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||
{
|
||
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
|
||
|
||
if (uploadTask) {
|
||
[self addDelegateForUploadTask:uploadTask
|
||
progress:uploadProgressBlock
|
||
completionHandler:completionHandler];
|
||
}
|
||
|
||
return uploadTask;
|
||
}
|
||
|
||
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
|
||
fromData:(NSData *)bodyData
|
||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||
{
|
||
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
|
||
|
||
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
|
||
|
||
return uploadTask;
|
||
}
|
||
|
||
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
|
||
progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
|
||
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
|
||
{
|
||
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithStreamedRequest:request];
|
||
|
||
[self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
|
||
|
||
return uploadTask;
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
|
||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||
{
|
||
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
|
||
|
||
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
|
||
|
||
return downloadTask;
|
||
}
|
||
|
||
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
|
||
progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
|
||
destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
|
||
completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
|
||
{
|
||
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithResumeData:resumeData];
|
||
|
||
[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
|
||
|
||
return downloadTask;
|
||
}
|
||
|
||
#pragma mark -
|
||
- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task {
|
||
return [[self delegateForTask:task] uploadProgress];
|
||
}
|
||
|
||
- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task {
|
||
return [[self delegateForTask:task] downloadProgress];
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
|
||
self.sessionDidBecomeInvalid = block;
|
||
}
|
||
|
||
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block {
|
||
self.sessionDidReceiveAuthenticationChallenge = block;
|
||
}
|
||
|
||
#if !TARGET_OS_OSX
|
||
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(void (^)(NSURLSession *session))block {
|
||
self.didFinishEventsForBackgroundURLSession = block;
|
||
}
|
||
#endif
|
||
|
||
#pragma mark -
|
||
|
||
- (void)setTaskNeedNewBodyStreamBlock:(NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block {
|
||
self.taskNeedNewBodyStream = block;
|
||
}
|
||
|
||
- (void)setTaskWillPerformHTTPRedirectionBlock:(NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block {
|
||
self.taskWillPerformHTTPRedirection = block;
|
||
}
|
||
|
||
- (void)setTaskDidSendBodyDataBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block {
|
||
self.taskDidSendBodyData = block;
|
||
}
|
||
|
||
- (void)setTaskDidCompleteBlock:(void (^)(NSURLSession *session, NSURLSessionTask *task, NSError *error))block {
|
||
self.taskDidComplete = block;
|
||
}
|
||
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
- (void)setTaskDidFinishCollectingMetricsBlock:(void (^)(NSURLSession * _Nonnull, NSURLSessionTask * _Nonnull, NSURLSessionTaskMetrics * _Nullable))block AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10)) {
|
||
self.taskDidFinishCollectingMetrics = block;
|
||
}
|
||
#endif
|
||
|
||
#pragma mark -
|
||
|
||
- (void)setDataTaskDidReceiveResponseBlock:(NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block {
|
||
self.dataTaskDidReceiveResponse = block;
|
||
}
|
||
|
||
- (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block {
|
||
self.dataTaskDidBecomeDownloadTask = block;
|
||
}
|
||
|
||
- (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block {
|
||
self.dataTaskDidReceiveData = block;
|
||
}
|
||
|
||
- (void)setDataTaskWillCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block {
|
||
self.dataTaskWillCacheResponse = block;
|
||
}
|
||
|
||
#pragma mark -
|
||
|
||
- (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block {
|
||
self.downloadTaskDidFinishDownloading = block;
|
||
}
|
||
|
||
- (void)setDownloadTaskDidWriteDataBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block {
|
||
self.downloadTaskDidWriteData = block;
|
||
}
|
||
|
||
- (void)setDownloadTaskDidResumeBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block {
|
||
self.downloadTaskDidResume = block;
|
||
}
|
||
|
||
#pragma mark - NSObject
|
||
|
||
- (NSString *)description {
|
||
return [NSString stringWithFormat:@"<%@: %p, session: %@, operationQueue: %@>", NSStringFromClass([self class]), self, self.session, self.operationQueue];
|
||
}
|
||
|
||
- (BOOL)respondsToSelector:(SEL)selector {
|
||
if (selector == @selector(URLSession:didReceiveChallenge:completionHandler:)) {
|
||
return self.sessionDidReceiveAuthenticationChallenge != nil;
|
||
} else if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
|
||
return self.taskWillPerformHTTPRedirection != nil;
|
||
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
|
||
return self.dataTaskDidReceiveResponse != nil;
|
||
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
|
||
return self.dataTaskWillCacheResponse != nil;
|
||
}
|
||
#if !TARGET_OS_OSX
|
||
else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
|
||
return self.didFinishEventsForBackgroundURLSession != nil;
|
||
}
|
||
#endif
|
||
|
||
return [[self class] instancesRespondToSelector:selector];
|
||
}
|
||
|
||
#pragma mark - NSURLSessionDelegate
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
didBecomeInvalidWithError:(NSError *)error
|
||
{
|
||
if (self.sessionDidBecomeInvalid) {
|
||
self.sessionDidBecomeInvalid(session, error);
|
||
}
|
||
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
|
||
{
|
||
NSAssert(self.sessionDidReceiveAuthenticationChallenge != nil, @"`respondsToSelector:` implementation forces `URLSession:didReceiveChallenge:completionHandler:` to be called only if `self.sessionDidReceiveAuthenticationChallenge` is not nil");
|
||
|
||
NSURLCredential *credential = nil;
|
||
NSURLSessionAuthChallengeDisposition disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
|
||
|
||
if (completionHandler) {
|
||
completionHandler(disposition, credential);
|
||
}
|
||
}
|
||
|
||
#pragma mark - NSURLSessionTaskDelegate
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||
newRequest:(NSURLRequest *)request
|
||
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
||
{
|
||
NSURLRequest *redirectRequest = request;
|
||
|
||
if (self.taskWillPerformHTTPRedirection) {
|
||
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
|
||
}
|
||
|
||
if (completionHandler) {
|
||
completionHandler(redirectRequest);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
|
||
{
|
||
BOOL evaluateServerTrust = NO;
|
||
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
|
||
NSURLCredential *credential = nil;
|
||
|
||
if (self.authenticationChallengeHandler) {
|
||
id result = self.authenticationChallengeHandler(session, task, challenge, completionHandler);
|
||
if (result == nil) {
|
||
return;
|
||
} else if ([result isKindOfClass:NSError.class]) {
|
||
objc_setAssociatedObject(task, AuthenticationChallengeErrorKey, result, OBJC_ASSOCIATION_RETAIN);
|
||
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
|
||
} else if ([result isKindOfClass:NSURLCredential.class]) {
|
||
credential = result;
|
||
disposition = NSURLSessionAuthChallengeUseCredential;
|
||
} else if ([result isKindOfClass:NSNumber.class]) {
|
||
disposition = [result integerValue];
|
||
NSAssert(disposition == NSURLSessionAuthChallengePerformDefaultHandling || disposition == NSURLSessionAuthChallengeCancelAuthenticationChallenge || disposition == NSURLSessionAuthChallengeRejectProtectionSpace, @"");
|
||
evaluateServerTrust = disposition == NSURLSessionAuthChallengePerformDefaultHandling && [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
|
||
} else {
|
||
@throw [NSException exceptionWithName:@"Invalid Return Value" reason:@"The return value from the authentication challenge handler must be nil, an NSError, an NSURLCredential or an NSNumber." userInfo:nil];
|
||
}
|
||
} else {
|
||
evaluateServerTrust = [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
|
||
}
|
||
|
||
if (evaluateServerTrust) {
|
||
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
|
||
disposition = NSURLSessionAuthChallengeUseCredential;
|
||
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
||
} else {
|
||
objc_setAssociatedObject(task, AuthenticationChallengeErrorKey,
|
||
[self serverTrustErrorForServerTrust:challenge.protectionSpace.serverTrust url:task.currentRequest.URL],
|
||
OBJC_ASSOCIATION_RETAIN);
|
||
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
|
||
}
|
||
}
|
||
|
||
if (completionHandler) {
|
||
completionHandler(disposition, credential);
|
||
}
|
||
}
|
||
|
||
- (nonnull NSError *)serverTrustErrorForServerTrust:(nullable SecTrustRef)serverTrust url:(nullable NSURL *)url
|
||
{
|
||
NSBundle *CFNetworkBundle = [NSBundle bundleWithIdentifier:@"com.apple.CFNetwork"];
|
||
NSString *defaultValue = @"The certificate for this server is invalid. You might be connecting to a server that is pretending to be “%@” which could put your confidential information at risk.";
|
||
NSString *descriptionFormat = NSLocalizedStringWithDefaultValue(@"Err-1202.w", nil, CFNetworkBundle, defaultValue, @"") ?: defaultValue;
|
||
NSString *localizedDescription = [descriptionFormat componentsSeparatedByString:@"%@"].count <= 2 ? [NSString localizedStringWithFormat:descriptionFormat, url.host] : descriptionFormat;
|
||
NSMutableDictionary *userInfo = [@{
|
||
NSLocalizedDescriptionKey: localizedDescription
|
||
} mutableCopy];
|
||
|
||
if (serverTrust) {
|
||
userInfo[NSURLErrorFailingURLPeerTrustErrorKey] = (__bridge id)serverTrust;
|
||
}
|
||
|
||
if (url) {
|
||
userInfo[NSURLErrorFailingURLErrorKey] = url;
|
||
|
||
if (url.absoluteString) {
|
||
userInfo[NSURLErrorFailingURLStringErrorKey] = url.absoluteString;
|
||
}
|
||
}
|
||
|
||
return [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorServerCertificateUntrusted userInfo:userInfo];
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
|
||
{
|
||
NSInputStream *inputStream = nil;
|
||
|
||
if (self.taskNeedNewBodyStream) {
|
||
inputStream = self.taskNeedNewBodyStream(session, task);
|
||
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
|
||
inputStream = [task.originalRequest.HTTPBodyStream copy];
|
||
}
|
||
|
||
if (completionHandler) {
|
||
completionHandler(inputStream);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didSendBodyData:(int64_t)bytesSent
|
||
totalBytesSent:(int64_t)totalBytesSent
|
||
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
||
{
|
||
|
||
int64_t totalUnitCount = totalBytesExpectedToSend;
|
||
if (totalUnitCount == NSURLSessionTransferSizeUnknown) {
|
||
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
|
||
if (contentLength) {
|
||
totalUnitCount = (int64_t) [contentLength longLongValue];
|
||
}
|
||
}
|
||
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
|
||
|
||
if (delegate) {
|
||
[delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
|
||
}
|
||
|
||
if (self.taskDidSendBodyData) {
|
||
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didCompleteWithError:(NSError *)error
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
|
||
|
||
// delegate may be nil when completing a task in the background
|
||
if (delegate) {
|
||
[delegate URLSession:session task:task didCompleteWithError:error];
|
||
|
||
[self removeDelegateForTask:task];
|
||
}
|
||
|
||
if (self.taskDidComplete) {
|
||
self.taskDidComplete(session, task, error);
|
||
}
|
||
}
|
||
|
||
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
|
||
- (void)URLSession:(NSURLSession *)session
|
||
task:(NSURLSessionTask *)task
|
||
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics AF_API_AVAILABLE(ios(10), macosx(10.12), watchos(3), tvos(10))
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
|
||
// Metrics may fire after URLSession:task:didCompleteWithError: is called, delegate may be nil
|
||
if (delegate) {
|
||
[delegate URLSession:session task:task didFinishCollectingMetrics:metrics];
|
||
}
|
||
|
||
if (self.taskDidFinishCollectingMetrics) {
|
||
self.taskDidFinishCollectingMetrics(session, task, metrics);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#pragma mark - NSURLSessionDataDelegate
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
dataTask:(NSURLSessionDataTask *)dataTask
|
||
didReceiveResponse:(NSURLResponse *)response
|
||
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
|
||
{
|
||
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
|
||
|
||
if (self.dataTaskDidReceiveResponse) {
|
||
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
|
||
}
|
||
|
||
if (completionHandler) {
|
||
completionHandler(disposition);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
dataTask:(NSURLSessionDataTask *)dataTask
|
||
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
|
||
if (delegate) {
|
||
[self removeDelegateForTask:dataTask];
|
||
[self setDelegate:delegate forTask:downloadTask];
|
||
}
|
||
|
||
if (self.dataTaskDidBecomeDownloadTask) {
|
||
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
dataTask:(NSURLSessionDataTask *)dataTask
|
||
didReceiveData:(NSData *)data
|
||
{
|
||
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
|
||
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
|
||
|
||
if (self.dataTaskDidReceiveData) {
|
||
self.dataTaskDidReceiveData(session, dataTask, data);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
dataTask:(NSURLSessionDataTask *)dataTask
|
||
willCacheResponse:(NSCachedURLResponse *)proposedResponse
|
||
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
|
||
{
|
||
NSCachedURLResponse *cachedResponse = proposedResponse;
|
||
|
||
if (self.dataTaskWillCacheResponse) {
|
||
cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
|
||
}
|
||
|
||
if (completionHandler) {
|
||
completionHandler(cachedResponse);
|
||
}
|
||
}
|
||
|
||
#if !TARGET_OS_OSX
|
||
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
|
||
if (self.didFinishEventsForBackgroundURLSession) {
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
self.didFinishEventsForBackgroundURLSession(session);
|
||
});
|
||
}
|
||
}
|
||
#endif
|
||
|
||
#pragma mark - NSURLSessionDownloadDelegate
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didFinishDownloadingToURL:(NSURL *)location
|
||
{
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||
if (self.downloadTaskDidFinishDownloading) {
|
||
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
|
||
if (fileURL) {
|
||
delegate.downloadFileURL = fileURL;
|
||
NSError *error = nil;
|
||
|
||
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
|
||
} else {
|
||
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidMoveFileSuccessfullyNotification object:downloadTask userInfo:nil];
|
||
}
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (delegate) {
|
||
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didWriteData:(int64_t)bytesWritten
|
||
totalBytesWritten:(int64_t)totalBytesWritten
|
||
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
|
||
{
|
||
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||
|
||
if (delegate) {
|
||
[delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
|
||
}
|
||
|
||
if (self.downloadTaskDidWriteData) {
|
||
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
|
||
}
|
||
}
|
||
|
||
- (void)URLSession:(NSURLSession *)session
|
||
downloadTask:(NSURLSessionDownloadTask *)downloadTask
|
||
didResumeAtOffset:(int64_t)fileOffset
|
||
expectedTotalBytes:(int64_t)expectedTotalBytes
|
||
{
|
||
|
||
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
|
||
|
||
if (delegate) {
|
||
[delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
|
||
}
|
||
|
||
if (self.downloadTaskDidResume) {
|
||
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
|
||
}
|
||
}
|
||
|
||
#pragma mark - NSSecureCoding
|
||
|
||
+ (BOOL)supportsSecureCoding {
|
||
return YES;
|
||
}
|
||
|
||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||
NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
|
||
|
||
self = [self initWithSessionConfiguration:configuration];
|
||
if (!self) {
|
||
return nil;
|
||
}
|
||
|
||
return self;
|
||
}
|
||
|
||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||
[coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"];
|
||
}
|
||
|
||
#pragma mark - NSCopying
|
||
|
||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||
return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];
|
||
}
|
||
|
||
@end
|