AFNetworking is a well-known tripartite library for the iOS world and has completely replaced ASI.The latest AFNetworking3.0 has also been switched from NSURLConnection to NSURLSession for ease of use.As an experienced iOSer who is constantly exploring, you should also look at the internal merits of source enhancement.
1. Overview
First, look at the structure of AFNetworking and its inheritance relationship:
Class | SuperClass | Description |
---|---|---|
AFURLSessionManager | NSObject | (1) Used to manage NSURLSession instances.(2) Responsible for generating dataTask, uploadTask and downloadTask. |
AFHTTPSessionManager | AFURLSessionManager | A subclass of AFURLSessionManager that encapsulates network requests and provides Convenience Methods to initiate HTTP requests. |
AFHTTPRequestSerializer | NSObject | Request required to generate network requests, including processing of parameters. |
AFHTTPResponseSerializer | NSObject | Resolve the returned Response and verify its validity. |
AFSecurityPolicy | NSObject | Mainly handles HTTPs communication. |
AFURLSessionManagerTaskDelegate | NSObject | As the delegate of the task, the callback is invoked. |
The main classes involved are listed in the table above, and the other auxiliary classes are briefly described below:
(1)_AFURLSessionTaskSwizzling: This class does one thing only: uses Method Swizzling to change the resume and suspend implementation of NSURLSessionDataTask and its parent class, and sends a message when it is called: AFNSURLSessionTaskDidResumeNotification and AFNSURLSessionTaskDidSuspendence Notification are:
- (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]; } }
(2)AFJSONRequestSerializer: A subclass of AFHTTPRequestSerializer which adds a check to whether parameters are in legal JSON format compared to AFHTTPRequestSerializer.In the case of POST, parameters are converted to NSData via NSJSONSerialization and placed in the HTTPBody.In addition, the header's Content-Type is set to application/json.
(3)AFQueryStringPair: Contains field and value attributes to represent parameters (eg. name='layne'), and field and value are processed by PercentEscaped with the following processing functions:
NSString * AFPercentEscapedStringFromString(NSString *string) { static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]]; // FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028 // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; static NSUInteger const batchSize = 50; NSUInteger index = 0; NSMutableString *escaped = @"".mutableCopy; while (index < string.length) { NSUInteger length = MIN(string.length - index, batchSize); NSRange range = NSMakeRange(index, length); // To avoid breaking up character sequences such as emoji range = [string rangeOfComposedCharacterSequencesForRange:range]; NSString *substring = [string substringWithRange:range]; NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; [escaped appendString:encoded]; index += range.length; } return escaped; }
Here are two points to explain:
(1) For the character truncation problem (eg.emoji), the following is used: rangeOfComposedCharacterSequencesForRange:, to prevent character truncation by adjusting the actual range to a given range.
(2) BachSize blocks are set here for escape.Why do you want to do this?FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028 gives specific explanations:
Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead.
Simply put, over 100 Chinese characters will hang on 8.1 and 8.2.
(4) AFStreaming MultipartFormData is used for multipart upload of formData.
(5)AFHTTPBodyPart
(6)AFMultipartBodyStream
(7)AFJSONResponseSerializer: A subclass of AFHTTPResponseSerializer that parses the response in JSON format.
(8)AFXMLParserResponseSerializer: a subclass of AFHTTPResponseSerializer that parses the response in NSXMLParser XML format.
(9)AFXMLDocumentResponseSerializer: a subclass of AFHTTPResponseSerializer that parses the response in NSXMLDocument XML format.
(10)AFPropertyListResponseSerializer: a subclass of AFHTTPResponseSerializer that parses the response in NSXMLDocument PropertyList format.
(11)AFImageResponseSerializer: A subclass of AFHTTPResponseSerializer that parses picture response s.
(12)AFCompoundResponseSerializer: A subclass of AFHTTPResponseSerializer that resolves the response of a composite type.
Class Analysis
1.AFURLSessionManager
AFURLSessionManager is the main class for managing network requests and is structured as follows:
-
Managed
A session(NSURLSession instance) that initiates a network request.
An operationQueue for performing proxy callbacks.
A responseSerializer (which implements AFURLResponseSerialization) for response resolution.
A security policy (AFSecurityPolicy instance) for HTTPs configuration.
A reachability manager (AFNetworkReachability Manager instance) for network connectivity monitoring.
-
Use getTasks WithCompletionHandler: to get session-managed tasks by overriding the getter methods of tasks, dataTasks, uploadTasks, and downloadTasks properties.
-
Provides a variety of functions to generate task s.For example:
-dataTaskWithRequest:completionHandler: -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: -uploadTaskWithRequest:fromFile:progress:completionHandler: -uploadTaskWithRequest:fromData:progress:completionHandler: -uploadTaskWithStreamedRequest:progress:completionHandler: -downloadTaskWithRequest:progress:destination:completionHandler: -downloadTaskWithResumeData:progress:destination:completionHandler:
-
Monitor upload/download progress.
-uploadProgressForTask: -downloadProgressForTask:
-
Define callback block properties, each corresponding to the NSURLSession-related delegate method.
@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) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge; @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; #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;
-
Constants are declared.
//notice AFNetworkingTaskDidResumeNotification AFNetworkingTaskDidCompleteNotification AFNetworkingTaskDidSuspendNotification AFURLSessionDidInvalidateNotification AFURLSessionDownloadTaskDidFailToMoveFileNotification //Notify the key of userInfo in AFNetworkingTaskDidCompleteNotification AFNetworkingTaskDidCompleteResponseDataKey AFNetworkingTaskDidCompleteSerializedResponseKey AFNetworkingTaskDidCompleteResponseSerializerKey AFNetworkingTaskDidCompleteAssetPathKey AFNetworkingTaskDidCompleteErrorKey
-
Generate a corresponding delegate(AFURLSessionManagerTaskDelegate instance) for each task at the time of task generation and save it in the mutableTaskDelegate sKeyedByTaskIdentifier in the form of {<taskID:delegate>}.
-
As a delegate for NSURLSession, the delegate methods implemented are:
/* ----------NSURLSessionDelegate---------- */ //Execute sessionDidBecomeInvalid block concurrent notification - (void)URLSession:didBecomeInvalidWithError: //Generate disposition(NSURLSessionAuthChallengeDisposition instance) and call completionHandler - (void)URLSession:didReceiveChallenge:completionHandler: //Execute didFinishEvents ForBackgroundURLSession block - (void)URLSessionDidFinishEventsForBackgroundURLSession: /* ----------NSURLSessionTaskDelegate---------- */ //Execute taskWillPerformHTTPRedirectionBlock to generate a new request and call completionHandler - (void)URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler: //Generate disposition(NSURLSessionAuthChallengeDisposition instance) and call completionHandler - (void)URLSession:task:didReceiveChallenge:completionHandler: //Generate inputStream(NSInputStream instance) and call completionHandler - (void)URLSession:task:needNewBodyStream: //Go to task delegate and execute taskDidSendBodyData block - (void)URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: //Go to task delegate and execute taskDidComplete block - (void)URLSession:task:didCompleteWithError: /* ----------NSURLSessionDataDelegate---------- */ //Execute the dataTaskDidReceiveResponse block to generate disposition and call completionHandler - (void)URLSession:dataTask:didReceiveResponse:completionHandler: //Reset task delegate and call dataTaskDidBecomeDownloadTask block - (void)URLSession:dataTask:didBecomeDownloadTask: //Go to task delegate and call dataTaskDidReceiveData block - (void)URLSession:dataTask:didReceiveData: //Execute the dataTaskWillCacheResponse block to generate the cacheResponse and call the completionHandler - (void)URLSession:dataTask:willCacheResponse:completionHandler: /* ----------NSURLSessionDownloadDelegate---------- */ //Go to task delegate and move the file - (void)URLSession:downloadTask:didFinishDownloadingToURL: //Go to task delegate and execute download TaskDidWriteData block - (void)URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: //Go to task delegate and execute download TaskDidResume block - (void)URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
2.AFURLSessionManagerTaskDelegate
AFURLSessionManagerTaskDelegate is a class that inherits from NSObject, not a protocol, although it is suffixed with /-Delegate.It and AFURLSessionManager are both defined in the file AFURLSessionManager.m.Its instance is used as a proxy for task.
-
Contains a manager property that uses a weak anaphora to use its AFURLSessionManager instance.
-
Contains uploadProgress and downloadProgress attributes that control upload and download progress (both instances of NSProgress), each fractionCompleted is monitored by KVO, and downloadProgressBlock and uploadProgressBlock are called at the end.
-
delegate implementations include:
/* ----------NSURLSessionTaskDelegate---------- */ //Construct userInfo, use manager's responseSerializer to resolve the response, and finally call self.completionHandler. - (void)URLSession:task:didCompleteWithError: //Update uploadProgress property - (void)URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: /* ----------NSURLSessionDataTask---------- */ //Update the downloadProgress property and save the received data with mutableData - (void)URLSession:dataTask:didReceiveData: /* ----------NSURLSessionDownloadTask-------- */ //Update downloadProgress property - (void)URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: //Update downloadProgress property - (void)URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: //Empty the downloadFileURL(nil) and move the file - (void)URLSession:downloadTask:didFinishDownloadingToURL:
Note: AFURLSessionManagerTaskDelegate instances do not own tasks themselves, and the proxy relationship between them is stored in the mutableTaskDelegate KeyedByTaskIdentifier as {<taskID:delegate>}.
3.AFHTTPSessionManager
AFHTTPSessionManager is a subclass of AFURLSessionManager, which encapsulates a more convenient method for HTTP requests.Its structure is as follows:
-
It mainly contains requestSerializer (instance of AFHTTPRequestSerializer) and responseSerializer (instance of AFHTTPResponseSerializer), which are used for encapsulation of requests and resolution of responses, respectively.
-
Provides three instance initialization methods:
+ (instancetype)manager; - (instancetype)initWithBaseURL:(nullable NSURL *)url; - (instancetype)initWithBaseURL:(nullable NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration;
The final call is to the third function.
-
The encapsulated Convenience Method is as follows:
- GET
- GET:parameters:headers:progress:success:failure:
- POST
- POST:parameters:headers:progress:success:failure: - POST:paramters:headers:constructingBodyWithBlock:progress:success:failure:
- HEAD
- HEAD:parameters:headers:success:failure:
- PUT
- PUT:parameters:headers:success:failure:
- PATCH
- PATCH:parameters:headers:success:failure:
- DELETE
- DELETE:paramaters:headers:success:failure:
Note: Only valid methods are listed above, others have been marked DEPRECATED_ATTRIBUTE.
-
In addition to containing.. constructingBodyWithBlock...In addition to POST functions, the rest of convenience methods generate corresponding dataTask s through the following functions:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod: URLString: parameters: uploadProgress: downloadProgress: success: failure:
In the above function, requestSerializer generates the request through HTTPMethod, URLString, and parameters, then calls the parent class: dataTaskWithRequest:uploadProgress:downloadProgress: completionHandler:Generate dataTask and return.The returned dataTask is started by resume.
4.AFHTTPRequestSerializer
AFHTTPRequestSerializer inherits from NSObject and encapsulates request.
-
The protocol AFURLRequestSerialization is implemented.This protocol has only one function:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error;
Used to include parameters in the original request to form a new request.
-
Save the data to be included in the request header using the array mutableHTTPRequestHeaders.Accept-Language and User-Agent are included by default.In order to ensure ASCII encoding rules when setting User-Agent, the author uses ICU text transformation.
CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)
ICU Library It provides a powerful set of text transformation functions that allow character conversion between different languages, such as Chinese characters to Pinyin.In the example above, the User-Agent field is first converted to Latin, then to Latin-ASCII, and finally to clear all characters that are not ASCII.Other transformations are available for reference ICU User Manual.
-
A KVO mechanism is used to monitor related attributes, which are recorded and added when request s are generated if the corresponding attributes are set by the user.
allowsCellularAccess cachePolicy HTTPShouldHandleCookies HTTPShouldUsePipelining networkServiceType timeoutInterval
- First override the + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key to disable automatic KVO triggering for the above six fields.
- Use willChangeValueForKey and didChangeValueForKey to trigger KVO manually in the setter of the above six fields.
-
Generate request using the following functions:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error
Operations performed include:
1. Create mutableRequest from URLString and method
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method;
(2) Use KVC to include six fields (user-set) of KVO monitoring into mutableRequest.
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
(3) Call the AFURLRequestSerialization protocol method - requestBySerializingRequest: withParameters: error:.Executed within this protocol method:
- Set the header of the request
- Format the parameters.The default format is name=layne$age=30&job=engineer.
- Add parameters to different locations of the request (URL or Body) depending on the requested Method(GET, POST, HEAD, and so on).
5.AFJSONRequestSerializer
AFJSONRequestSerializer is a subclass of AFHTTPRequestSerializer that uses NSJSONSerialization to encode parameters into JSON format and set Content-Type to application/json.It overrides the AFURLRequestSerialization protocol method:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; }//If it is a GET/HEAD/DELETE method, since the parameters are stitched together in the URL, it does not matter if the JSON is not json, just call the method of the parent class directly. NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }];//Update header data if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; }//Set the Content-Type field to "application/json" if (![NSJSONSerialization isValidJSONObject:parameters]) { if (error) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)}; *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; } return nil; }//Illegal json format (NSDictionary) data NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];//json serialization if (!jsonData) { return nil; } [mutableRequest setHTTPBody:jsonData]; } return mutableRequest; }
6.AFHTTPResponseSerializer
AFHTTPResonseSerializer inherits from NSObject and implements the AFURLResponseSerialization protocol:
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error;
The validity of response is judged by acceptableStatusCodes and acceptableContentTypes in the protocol method.
7.AFJSONResponseSerializer
AFJSONResponseSerializer is a subclass of AFHTTPResponseSerializer.
-
Set acceptableContentTypes to specify legal content-type:
application/json text/json text/javascript
-
Override AFURLResponseSerialization protocol method:
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error;
Inside:
(1) Judging the validity of response according to acceptableStatusCodes and acceptableContentTypes;
(2) Converting data to NSDictionary using NSJSONSerialization
(3) The value of removesKeysWithNullValues determines whether to clear NSNull data from NSDictionary.
These are the main class structures and functions of AFNetworking.In the next blog we'll walk through the logic with a simple POST request to see how AFN works.