What exactly is AFNetworking like

Keywords: iOS JSON network ascii

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.

Posted by Najjar on Thu, 05 Dec 2019 18:46:01 -0800