AFURLSession Manager is absolutely the core of AFNetworking.
- Responsible for creating and managing NSURLSession
- Managing NSURLSession Task
- Implementation of proxy methods in NSURLSession Delegate and other protocols
- Managing progress using AFURLSession Manager Task Delegate
- Using _AFURLSession TaskSwizzling Adjustment Method
- Introducing AFSecurity Policy to Ensure the Security of Requests
- Introducing AFNetwork Reachability Manager to monitor network status
Here we will focus on the first five of the above seven functions and analyze how they are packaged. NSURLSession and many proxy methods.
Create and manage NSURLSession
When using AFURLSession Manager, the first thing to do is to initialize:
- (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.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; self.responseSerializer = [AFJSONResponseSerializer serializer]; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; #1: Set up agents for existing task s. return self; }
In the initialization method, we need to complete the initialization of some instances we own:
- Initialize session configuration (NSURLSession Configuration), default Session Configuration
- Initialize the session and set up the session's proxy and proxy queue
- Examples of AFJSONResponse Serializer, AFSecurity Policy, and AFNetwork Reachability Manager
- Initialize the dictionary that saves data task (mutable Task Delegates KeyedByTaskIdentifier)
Managing NSURLSession Task
Next, after obtaining an example of AFURLSession Manager, we can create an instance of NSURLSession DataTask by:
- (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; - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; ... - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; ...
Some methods for returning NSURLSessionTask are omitted here, because these interfaces are in the same form.
We will take the implementation of the -[AFURLSession Manager dataTaskWithRequest: uploadProgress: download Progress: completionHandler:] method as an example to analyze how it instantiates and returns a Examples of NSURLSessionTask:
- (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 { __block NSURLSessionDataTask *dataTask = nil; url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; }
Note: url_session_manager_create_task_safe is called because of a bug in the Apple Framework #2093 If you're interested in it, it's not explained here.
- Invoke the - [NSURLSession dataTaskWithRequest:] method to pass in NSURLRequest
- Calling the method - [AFURLSession Manager addDelegate ForDataTask: uploadProgress: downloadProgress: completionHandler:] returns an AFURLSession Manager TaskDelegate object
- Pass completionHandler uploadProgressBlock and download ProgressBlock into the object and call back when the corresponding event occurs
- (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] init]; delegate.manager = self; delegate.completionHandler = completionHandler; dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }
In this method, another method - [AFURLSession Manager setDelegate: forTask:] is called to set up the agent at the same time:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { #1: Check parameters, Brief [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; [delegate setupProgressForTask:task]; [self addNotificationObserverForTask:task]; [self.lock unlock]; }
As mentioned above, AFNRUSession Manager stores and manages each NSURLSession Task through the dictionary mutable Task Delegates KeyedByTaskIdentifier, which uses task Identifier. Store task s for keys.
This method uses NSLock to ensure the use of different threads When mutable Task Delegates KeyedByTaskIdentifier, there is no thread competition problem.
Simultaneous invocation - setupProgressForTask: We will introduce this method in the body below.
Implementation of proxy methods in NSURLSession Delegate and other protocols
As you can see in the header file of AFURLSession Manager, it follows a number of protocols, including:
- NSURLSessionDelegate
- NSURLSessionTaskDelegate
- NSURLSessionDataDelegate
- NSURLSessionDownloadDelegate
It points the agent of NSURLSession to self in the initialization method - [AFURLSession Manager initWithSession Configuration:], and then implements these methods to provide more concise block interface:
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block; - (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block; ...
It provides the corresponding block interface for all proxy protocols. The idea of implementation is similar.- [AFNRLSession Manager setSession Did BecomeInvalid Block:] For example.
First, the setter method is called and the block is stored in the session DidBecomeInvalid property:
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block { self.sessionDidBecomeInvalid = block; }
When a proxy method is called, if there is a corresponding block, the corresponding block is executed:
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { if (self.sessionDidBecomeInvalid) { self.sessionDidBecomeInvalid(session, error); } [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session]; }
Other similar interface implementations are similar, skipped here directly.
Managing progress using AFURLSession Manager Task Delegate
We mentioned the AFURLSession Manager TaskDelegate class above, which provides schedule management for task and calls back at the end of task, that is to say, call back at the end of task.- [AFURLSession Manager data Task WithRequest: uploadProgress: download Progress: completionHandler:] and other methods passed in the completionHandler.
Let's first analyze how AFURLSession Manager Task Delegate tracks progress:
- (void)setupProgressForTask:(NSURLSessionTask *)task { #1: Set up callbacks when upload progress or download progress status changes #2: KVO }
The implementation of this method has two parts, one is to set callbacks to the two attributes uploadProgress and download Progress held by the agent.
__weak __typeof__(task) weakTask = task; self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; [self.uploadProgress setCancellable:YES]; [self.uploadProgress setCancellationHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask cancel]; }]; [self.uploadProgress setPausable:YES]; [self.uploadProgress setPausingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask suspend]; }]; if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.uploadProgress setResumingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }]; }
Only the code to set the callback for uploadProgress is set here, and the setup for download Progress is exactly the same as here.
The main purpose is to call resume when the state of NSProgress changes
suspend and other methods change the state of task.
[task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived)) options:NSKeyValueObservingOptionNew context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive)) options:NSKeyValueObservingOptionNew context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) options:NSKeyValueObservingOptionNew context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) options:NSKeyValueObservingOptionNew context:NULL]; [self.downloadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; [self.uploadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL];
Change progress in observeValueForKeypath:ofObject:change:context: method and call block
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { if ([object isKindOfClass:[NSURLSessionTask class]]) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue]; } } else if ([object isEqual:self.downloadProgress]) { if (self.downloadProgressBlock) { self.downloadProgressBlock(object); } } else if ([object isEqual:self.uploadProgress]) { if (self.uploadProgressBlock) { self.uploadProgressBlock(object); } } }
Update the NSProgress object when some attributes of the object change or pass the NSProgress object self.uploadProgressBlock(object) with a block.
The proxy method URLSession:task:didCompleteWithError:
At the end of each NSURLSession Task, the proxy method URLSession:task:didCompleteWithError:
- Call the incoming completionHander block
- Issue AF Networking Task Did Complete Notification Notification Notification Notification
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { #1: Get data and store `responseSerializer'and `download File URL'.` if (error) { #2: Call `completionHandler'in case of an error` } else { #3: Call `completionHandler'` } }
This is the skeleton of the whole proxy method. Let's first look at the simplest part of the code:
__block 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 (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data; }
This part of the code takes data from mutableData and sets userInfo.
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]; }); });
Use them if the current manager holds completionGroup or completionQueue. Otherwise, a dispatch_group_t will be created. And call completionHandler in the main thread and send notifications (in the main thread).
If there are no errors in executing the current task, serialize the data first, then call the block again and send notifications.
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]; }); }); });
The proxy method URLSession:dataTask:didReceiveData: and - URLSession: downloadTask: didFinish DownloadingToURL:
These two proxy methods are invoked when the data is received or when the corresponding files are downloaded. The functions of these two proxy methods are to add data to mutableData and process the downloaded files respectively:
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.mutableData appendData:data]; } - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSError *fileManagerError = nil; self.downloadFileURL = nil; if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]; if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } } }
Using _AFURLSession TaskSwizzling Adjustment Method
_ The only function of AFURLSession TaskSwizzling is modification. NSURLSession Task The resume and suspend methods replace the original implementation with the following methods
- (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]; } }
The purpose of this is to notify when the method resume or suspend is called.
The specific method of dispensing is carried out in the + load method.
The load method is called only once when the entire file is introduced
+ (void)load { if (NSClassFromString(@"NSURLSessionTask")) { 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]; } }
- First, the NSClassFromString (@ "NSURLSession Task") is used to determine whether the currently deployed iOS version contains the NSURLSession Task class.
- Because the implementation of NSURLSessionTask on iOS 7 and iOS 8 is different, a NSURLSession dataTask with URL:] method is used to return a NSURLSessionTask. Example
- Get the implementation af_resume in the current class _AFURLSession TaskSwizzling
- If the current class currentClass has resume methods
- True: 5
- False: 7
- Use swizzleResume AndSuspendMethodForClass: Adjust the resume and the class suspend method
- currentClass = [currentClass superclass]
Introducing AFSecurity Policy to Ensure the Security of Requests
AFSecurity Policy is a class used by AFNetworking to secure HTTP requests. It is called AFSecurity Policy. AFURLSession Manager holds if you are in Search in the Implementation File of AFURLSession Manager Self. security policy, you only get three results:
- Initialize self. security policy = [AFSecurity Policy default policy]
- When receiving authentication requests from the connection layer
- When a task receives a validation request
On API calls, the latter two call the -[AFSecurity Policy evaluateServer Trust: for Domain:] method to determine whether the current server is trusted or not. We will introduce the implementation of this method in the next article.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.taskDidReceiveAuthenticationChallenge) { disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeRejectProtectionSpace; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } }
If no taskDidReceive Authentication Challenge block is passed in, authentication credentials will be obtained only when the above method returns YES. credential.
Introducing AFNetwork Reachability Manager to monitor network status
Like AFSecurity Policy, AFURLSession Manager monitors network status by AFNetwork Reachability Manager To be responsible, it simply holds an object of AFNetwork Reachability Manager.
When we really need to judge the network state, we still need the developer to call the corresponding API to get the network state.
Summary
- AFURLSession Manager is the encapsulation of NSURLSession
- It creates instances of NSURLSession DataTask through interfaces such as -[AFURLSession Manager dataTaskWithRequest: completionHandler:]
- Holding a dictionary mutable Task Delegates KeyedByTaskIdentifier manages these data task instances
- Introduce AFURLSession Manager Task Delegate to load the incoming uploadProgress Block download Progress Block completion Handler Call at the right time
- Implementing all proxy methods to provide block interface
- Notification is given when the data task status changes by way of dispensing
Other articles on other AFNetworking source code analysis:
- Overview of AF Networking (I)
- AFURLSession Manager, the Core of AFNetworking (II)
- Processing requests and responding to AFURLSerialization (III)
- AFNetwork Reachability Manager Monitors Network Status (IV)
- Certificates to verify HTTPS requests (V)