This article belongs to “Jane Book — Liu Xiaozhuang” original, please note:

A Simple Book — Liu Xiaozhuang


AFNetworking source code analysis

AFNetworking is the most commonly used networking framework for iOS. Although the system also has NSURLSession, we generally don’t use it directly. AFNetworking has gone through three major versions, and most are now in 3.x.

AFNetworking went through the following three stages:

  • Version 1.0: Based onNSURLConnectionThe encapsulation.
  • Version 2.0: Two sets of implementations, one based onNSURLConnectionandNSURLSession, it is toNSURLSessionA transitional version of.
  • Version 3.0: Based onNSURLSessionThe encapsulation.

File a

The structure of AfNetworking3. X is very simple, with four main parts and some OTHER UIKit-based categories, but these are not standard.

  • Manager: the two responsible for handling network requestsManagerThe major implementations are thereAFURLSessionManagerIn the.
  • Reachability: Monitors network status.
  • Security: Deals with network SecurityHTTPSRelated.
  • Serialization: Formatter that requests and returns data.

AFURLSessionManager

In AFN3.0, the manager of network request mainly consists of AFHTTPSessionManager and AFURLSessionManager, which are the parent-child relationship. The responsibilities of the two classes are clearly divided, with the parent class handling some basic network request code and accepting NSURLRequest objects, and the subclass handling HTTP protocol logic.

This set of AFN design is very easy to expand, if you want to increase the FTP protocol processing, you can create a subclass based on AFURLSessionManager. A subclass requires very little code handling. An NSURLRequest object is created and the parent class is called to perform specific request operations.

Create our sessionManager

AFHTTPSessionManager initialization method of a class is not too much code, its internal call is the parent class AFURLSessionManager initWithSessionConfiguration method, the following are some of the key code inside this method.

There is a parameter sessionConfiguration in the initialization method. If it is not passed in, the default is to use the system defaultConfiguration. We generally do not customize the configuration, so most of them are system.

if(! configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
Copy the code

This is followed by the initialization code for NSURLSession, which is explained in more detail later on NSOperationQueue. NSURLSession can be initialized in two ways: one is to use the shared session of the system, the other is to create a session. The AFN chooses to create its own session and creates a separate session for each request.

NSURLSession can be used for connection multiplexing, which avoids a lot of handshaking and waving, and improves the speed of network requests. Apple allows four connections to a domain name on iOS devices. However, since the IMPLEMENTATION of AFN creates a session for each request, connection reuse is not possible.

Therefore, AFN can be re-encapsulated externally, and AFHTTPSessionManager can be reused as a singleton object, and connections can be reused by reusing sessionManager. However, this solution requires special compatibility for different requestSerializer and responseSerializer. Therefore, it is best to create a sessionManager pool and reuse the same type of sessionManager directly. Otherwise, create a new one.

// Share the session connection pool
[NSURLSession sharedSession];
// When a new session is created, the shared session connection pool cannot be used
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
Copy the code

Since all sessionTask requests of the current AFURLSessionManager object share the same callback agent, AFN distinguishes each sessionTask by using the following variable dictionary. All taskdelegates and task.taskidentifier are mapped one by one to make it easy to operate on each requested task.

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
Copy the code

Can be found in the initialization method, AFN, after creating the session call getTasksWithCompletionHandler method to get all the current task. But now that the session has just been created, there should theoretically be no Task. However, the answer can be found in the issues of AFN.

This is because, in the completionHandler callback, the previous task is iterated over and the callbacks are set to nil in order to prevent the task recovered by the session ID from causing some crash problems when it comes to the foreground.

[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]; }}];Copy the code

Create a task

Tasks are created in AFURLSessionManager. There are three types of tasks: dataTask, uploadTask and downloadTask. AFN does not process streamTask.

AFHTTPSessionManager essentially calls one of the following methods or similar methods when creating GET, POST, etc., and creates a task inside the method. And call the addDelegateForDataTask will at the back of the handle to AFURLSessionManagerTaskDelegate to complete. The task is then returned to the caller, who, after acquiring the task object, subclass AFHTTPSessionManager, calls the Resume method to begin the request.

- (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;
}
Copy the code

In addition to normal requests, upload and Download are handled similarly.

In the addDelegateForDataTask method, the setDelegate:forTask: method of sessionManager is called, which internally registers the Task and taskDelegate. Because AFN can let the outside world listen for the status of requests through notifications, the Task’s Resume and suspend events are also listened for in this method and broadcast in the implementation code.

- (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;
}
Copy the code

If you start with the AFHTTPSessionManager creation task and follow the code logic here, you will find that the AFN3.0 request code is really simple. It is mainly focused on creating NSMutableURLRequest, and the rest depends on NSURLSession. Because it’s true that the NSURLSession API is fairly encapsulated and easy to use.

What AFN3.0 does is encapsulate NSURLSession so you don’t have to write repetitive code and it’s easy to get callback results from blocks.

AFURLSessionManagerTaskDelegate

NSURLSession callback method is more, here only for some key code to explain, as well as comb the overall callback logic, not a list of the role of each callback method, detailed source code you can directly download AFN code to view.

In AFURLSessionManager, have a AFURLSessionManagerTaskDelegate class is more important, this class and sessionTask is one-to-one, responsible for handling sessionTask request a lot of logic, The NSURLSessionDelegate callback is basically forwarded to the taskDelegate to handle. HTTPS certificate validation, download progress, and so on are handled in the NSURLSession callback without too much complexity.

TaskDelegate is nicely designed to objectifying the handling of the agent callback task, as well as to slim down the AFURLSessionManager class. It would be ideal to set the agent directly as a taskDelegate, but since some of the processing logic of AFURLSessionManager itself is involved, it is designed for messaging.

TaskDelegate functions are simple, mainly handling NSData data, NSProgress upload and download progress, and notification parameters. When performing AFN download processing, NSData data concatenation, event callback, and file processing are all handled by taskDelegate.

Here is the code for processing the downloadTask when it completes, and the other callbacks are not listed.

- (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]) {
                [[NSNotificationCenterdefaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; }}}}Copy the code

A nice design in taskDelegate is that taskDelegate does not do concatenation and callbacks directly in the NSURLSession delegate method. Instead, the upload and download tasks correspond to different NSProgress, and the fractionCompleted property is listened to through KVO, and state callbacks such as Cancel and suspend are implemented. The status and progress of the task are entrusted to NSProgress, and the progress of NSProgress is directly spliced in the callback method to call back THE KVO method.

The cancel, pause, and resume methods in NSProgress correspond exactly to the sessionTask method call. From a code point of view, the AFN doesn’t seem to make any calls, but it’s a good idea.

_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(iOS 9, 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];
}
Copy the code

_AFURLSessionTaskSwizzling

Read the source code, can be found in the AFURLSessionManager another _AFURLSessionTaskSwizzling class, here we are referred to as “taskSwizzling class. The main function of this class is to swizzling in the +load method, replace the dataTask resume and suspend methods, and issue corresponding notifications in the replaced method, without much practical function.

The taskSwizzling class, however, has some good code design lessons to learn. Since sessionTask has a series of inheritance chains, swizzling sessionTask doesn’t work with other subclasses because each subclass has its own implementation. And writing a lot of swizzling isn’t very technical.

In iOS7 and iOS8, sessionTask inheritance relationship is not the same, it is best to conduct a unified processing. AFN takes the approach of creating a dataTask object, swizzling the object, and traversing its inheritance chain to keep swizzling, thus ensuring the correctness of the integrated inheritance chain.

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];
}

+ (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)); }}Copy the code

Clang precompile instruction

In order to avoid compiler warnings, AFN uses precompilation instructions to modify the code. The precompilation instructions are basically composed of three types: push, POP and ignored. Github maintains a list of Clang warnings. If you want to precompile them, you can check them out.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
Copy the code

Threading issues

NSURLSession can create multiple tasks concurrently in iOS8, but there is a problem of duplicate identifiers when setting task identifiers concurrently. To solve this problem, below iOS8, the system places all sessionTask creation in a synchronous serial queue, ensuring that the creation and assignment operations are serial.

url_session_manager_create_task_safely(^{
    dataTask = [self.session dataTaskWithRequest:request];
});

url_session_manager_create_task_safely(^{
    uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});

// If Foundation is smaller than iOS8, block tasks are executed in a synchronization queue. This problem is due to the possibility of having multiple identifiers for concurrent creation of tasks below iOS8
static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else{ block(); }}Copy the code

One of the more interesting is that AFN, in order to let developers understand why to add this judgment, the judgment of iOS8 system has been defined as a macro, and with Apple Support ID as the macro definition named, very well-known meaning.

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
Copy the code

When the AFN calls back to the didCompleteWithError method and processes the returned data, it switches to other threads and groups for processing, and then switches to the main thread and notifies the caller.

The AFN provides two properties that set the Dispatch Queue and Dispatch Group to be called back after the request ends. If not, the AFN has a default implementation to handle the end of the request. The following is the implementation of group and queue, AFN for the processing of returned data, using concurrent processing.

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;
}
Copy the code

NSOperationQueue

The AFN sets the maximum concurrency to 1 when creating the operationQueue of the AFURLSessionManager. This is because when creating NSURLSSession, Apple requires the sequential execution of data requested by the network. In order to ensure the sequential execution of proxy methods, the proxy method of NSURLSSession needs to be called serial.

AFHTTPSessionManager

AFHTTPSessionManager is essentially the encapsulation of the parent class AFURLSessionManager, the main implementation is in the parent class, its own internal code implementation is very simple. When AFHTTPSessionManager is created, a baseURL is passed in and the requestSerializer and responseSerializer objects are specified.

From the point of view of the code implementation, AFN requests are not singletons; each request creates a new request object. The usually called GET, POST and other network request methods are defined in AFHTTPSessionManager. AFHTTPSessionManager internally calls the parent method, initiates the response request, obtains the task object, calls the task resume and returns it to the caller.

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void(^) (NSProgress * _Nonnull))downloadProgress
                      success:(void(^) (NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void(^) (NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{

    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    [dataTask resume];

    return dataTask;
}
Copy the code

AFURLRequestSerialization

AFURLRequestSerialization responsible for creating NSMutableURLRequest request object and request of the request parameter matching, set the cache strategy, setting up top on request related configuration.

AFURLRequestSerialization is not a class, but a file, which contains three requestSerializer request object, corresponding to different requests serializer.

  • AFHTTPRequestSerializer: common request.
  • AFJSONRequestSerializer:JSONThe request.
  • AFPropertyListRequestSerializer: a kind of specialxmlFormat request.

The difference between these three classes is that the content-Type is different, but everything else is basically the same. AFN is HTTP by default.

AFURLRequestSerialization agreement

In the file defines the name AFURLRequestSerialization agreement, different requestSerializer has the different implementation method of agreement, the following is AFHTTPRequestSerializer implementation code. When creating requestSerializer, set the public parameters of the request header, convert the parameters to NSData through NSJSONSerialization, and assign them to the httpBody of the request object. The core code is condensed below.

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    if (parameters) {
        if(! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }

        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
        [mutableRequest setHTTPBody:jsonData];
    }

    return mutableRequest;
}
Copy the code

The AFN requestManager does not provide the code to set the request header. If you want to set the parameters for a network request, you need to add the parameters through the API exposed by requestSerializer. RequestSerializer allows you to add, delete, and delete request headers.

From create AFURLRequestSerialization object to finally return NSURLRequest object, in the middle of the process is not complicated, mainly set the request header and stitching parameters, logic is clear.

AFQueryStringPair

AFURLRequestSerialization has a very important function is the processing parameters, AFQueryStringPair is responsible for dealing with these parameters. The pair class defines two properties that correspond to the key and value of the request parameter. In addition, some very useful C language functions are defined.

@interface AFQueryStringPair : NSObject
@property (readwrite.nonatomic.strong) id field;
@property (readwrite.nonatomic.strong) id value;

- (id)initWithField:(id)field value:(id)value;

- (NSString *)URLEncodedStringValue;
@end
Copy the code

AFQueryStringFromParameters function is responsible for the dictionary, the request parameters to splice in the URL string at the back of the parameters, the function is key to AFQueryStringPair class defined in a function. Function within AFQueryStringPairsFromDictionary function parameters from the dictionary, to a storage array pair object in hand traveled across calendar, after traversal call URLEncodedStringValue method joining together the parameters, and eventually become a string argument.

The URLEncodedStringValue method is simple to implement. It simply concatenates the key and value, and adds “=” in the middle.

static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@ "&"];
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@ "% @ = % @", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; }}Copy the code

Here is the code for the concatenation of arguments, which internally converts the original argument to the type of AFQueryStringPair, but with the same hierarchy. The key and value of the dictionary in the last layer are converted into objects of type pair, and the array of nested pair objects is returned to the caller.

The object hierarchy remains the same, but dictionaries and collections are converted to array structures. That is, dictionaries, arrays and dictionaries are passed into nested structures and returned as arrays, arrays and pairs.

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@ % @ [% @] "", key, nestedKey] : nestedKey), nestedValue)]; }}}else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@ % @ "[]", key], nestedValue)]; }}else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in[set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; }}else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}
Copy the code

Set the NSMutableURLRequest

AFHTTPRequestSerializer When creating NSMutableURLRequest, you need to set the request attribute. Serializer provides the same attributes as request. You can directly invoke the serializer to set request attributes.

AFHTTPRequestSerializer internal create request, not according to set properties for the request according to the assignment, but by an attribute array AFHTTPRequestSerializerObservedKeyPaths, Put the serializer attributes assigned to request in an array.

static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
    });

    return _AFHTTPRequestSerializerObservedKeyPaths;
}
Copy the code

To initialize AFHTTPRequestSerializer, iterate through the keyPath array and listen for the serializer assignment via KVO. If the outside world to assignment of serializer corresponding attributes, then add it to the mutableObservedChangedKeyPaths array. When creating the request object is that traverse mutableObservedChangedKeyPaths array and the value assigned to the request object.

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
        [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNewcontext:AFHTTPRequestSerializerObserverContext]; }} - (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else{[self.mutableObservedChangedKeyPaths addObject:keyPath]; }}}for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
        [mutableRequest setValue:[selfvalueForKeyPath:keyPath] forKey:keyPath]; }}Copy the code

The form submission

When submitting a POST form, the AFMultipartFormData protocol is used. When the POST method is called, an object that complies with this protocol is called back through which form submission operations can be performed.

[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
    [formData appendPartWithFileData:params[@"front_img"]
                                name:@"front_img"
                            fileName:frontImgfileName
                            mimeType:@"multipart/form-data"];
    [formData appendPartWithFileData:params[@"reverse_img"]
                                name:@"reverse_img"
                            fileName:reverseImgfileName
                            mimeType:@"multipart/form-data"];
    [formData appendPartWithFileData:params[@"face_img"]
                                name:@"face_img"
                            fileName:faceImgfileName
                            mimeType:@"multipart/form-data"];

} progress:^(NSProgress * _Nonnull uploadProgress) {
    // nothing
} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    // nothing
} failure:nil];
Copy the code

When submitting a form, you can pass in a file directly or a path. Form submission allows multiple files to be submitted at the same time, theoretically unlimited in number.

Caching strategies

AFN’s cache policy is consistent with NSURLCache’s cache policy and uses enumerations directly from the system, which is very friendly for iOS developers. Here are the enumerations definitions, ignoring some unimplemented ones, and some redirected to existing enumerations, all available here.

typedef NS_ENUM(NSUInteger.NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0.NSURLRequestReloadIgnoringLocalCacheData = 1.NSURLRequestReturnCacheDataElseLoad = 2.NSURLRequestReturnCacheDataDontLoad = 3.NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4.NSURLRequestReloadRevalidatingCacheData = 5};Copy the code
  • NSURLRequestUseProtocolCachePolicy, use the caching policy specified by the agreement.
  • NSURLRequestReloadIgnoringLocalCacheData, ignore the cache, a request directly.
  • NSURLRequestReturnCacheDataElseLoad, not validate cache expiration time, use the cached data if there is, if there is no request to the server.
  • NSURLRequestReturnCacheDataDontLoad, not validate cache expiration time, use the cached data if there is, if there is no attempt failed.
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData, ignoring the local cache, and agents of intermediate medium cache.
  • NSURLRequestReloadRevalidatingCacheData, and data source server validation data validity, if can use directly use the cached data, otherwise the request data from the server.

AFURLResponseSerialization

AFURLResponseSerialization to deal with the logic of the response, the main function is to set up acceptType, coding format and return the data processing server. Similarly, AFURLResponseSerialization also has the same name agreement, each subclass follow proxy method and implement different return values handling code.

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error;
Copy the code

Like AFURLRequestSerialization, AFURLResponseSerialization made up of a parent class and six children, one of the subclass is Mac, so here don’t do analysis, Subclasses are only responsible for modifying acceptTypes and handling specific returned data.

  • AFHTTPResponseSerializer: public parent class. The return value type for processing isNSDataThe binary.
  • AFJSONResponseSerializer:JSONReturns data, also the default type.
  • AFXMLParserResponseSerializer, processingXMLReturns data by the systemNSXMLParserResponsible for handling.
  • AFPropertyListResponseSerializer: special processingXMLReturn data, that isplistThe data.
  • AFImageResponseSerializer: processing image data returned, this type is used.
  • AFCompoundResponseSerializer: processing complex data, have many types to return the result.

Fault-tolerant processing

Since the server sometimes returns null, the system converts it to an NSNull object, and sending an incorrect message to an NSNull object causes a crash. Upon receiving the return value from the server, the AFN performs a recursive lookup on the return value, finds all NSNull objects and removes them to prevent a crash caused by sending a message to an NSNull object.

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        return (readingOptions & NSJSONReadingMutableContainers)? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            if(! value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions); }}return (readingOptions & NSJSONReadingMutableContainers)? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}
Copy the code

Design skills of AFNetworking

bundleForClass

When using NSBundle objects, the most common way to fetch the bundle is mainBundle or bundleWithPath, which is fine when it is read from the app binary. But it’s not so easy to use when it comes to framework dynamic libraries.

The framework can contain resource files, such as.bundle files. If the framework is in dynamic library form (the framework also has static form), it is represented as a separate binary and allocated a separate binary space. When reading bundles, consider using bundleForClass.

BundleForClass means to read the NSBundle file from the package in which the binary of the current class is defined. For example, app is read from the main bundle, or if it’s the framework it’s read from the binary it’s in.

Network indicator

AFN provides some UIKit Category, such as a network request, the network turn indicator chrysanthemum, by AFNetworkActivityIndicatorManager class is responsible for. Turning on the network indicator is as simple as adding the following code, which turns it off by default.

[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
Copy the code

Wrong AFNetworkActivityIndicatorManager code too much analysis here, the only one of the more important point to analysis, the following a general designation for indicatorManager.

Before _AFURLSessionTaskSwizzling wrote a lot of the code from the class, so that I can send resume and suspend two notice, the two notification in indicatorManager used to. The network indicator listens for and is driven entirely by the following three notifications.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
Copy the code

If you look at the source code of indicatorManager, you will find why there is a Timer in it. It is not necessary at all. If there is a network request, it will switch to chrysanthemum, and if there is no network request, it will stop.

This is because AFN considers that if a network request is very fast, it will lead to the situation that the chrysanthemum will turn and disappear quickly, and if there are many network requests, it will flash many times. Therefore, for this problem, indicatorManager realizes through the way of Timer, if the network request has ended in the specified interval, it does not show the chrysanthemum, and if there are multiple requests, it does not interrupt between the requests.

Set it to 1.0 seconds for the start of the turn and 0.17 seconds for the end. When the chrysanthemums start to rotate, there needs to be a delay of 1.0 seconds, which is long enough to ensure that the previous chrysanthemums stop rotating. The end of the circle will be 0.17 seconds later, to ensure that the chrysanthemum rotation will be at least 0.17 seconds.

static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;

- (void)startActivationDelayTimer {
    self.activationDelayTimer = [NSTimer
                                 timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
    [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}

- (void)startCompletionDelayTimer {
    [self.completionDelayTimer invalidate];
    self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
    [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
Copy the code

Since indicatorManager uses notification callbacks, all network request notifications will be called here. So when multiple network requests come in, they are counted by an _activityCount, which can be thought of as a queue to make it easier to understand. When the network indicator is displayed, it is based on _activityCount, and the network indicator is displayed if there are requests in the queue, no matter how many requests there are.

This is a good design idea and can be used in many places in the project. For example, some methods need to be called in pairs, such as play-start and pausing, and if one method is called more than once it can cause a bug. This way is more suitable for fault tolerance in the way of count, internal for count to do some judgment operations.

- (void)incrementActivityCount {
    [self willChangeValueForKey:@"activityCount"];
	@synchronized(self) {
		_activityCount++;
	}
    [self didChangeValueForKey:@"activityCount"];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}

- (void)decrementActivityCount {
    [self willChangeValueForKey:@"activityCount"];
	@synchronized(self) {
		_activityCount = MAX(_activityCount - 1.0);
	}
    [self didChangeValueForKey:@"activityCount"];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}
Copy the code

IndicatorManager is multi-thread safe and locks in some key places by means of synchronized to prevent resource snatches caused by notifications called from various threads.

AFSecurityPolicy

Validation processing

AFN supports HTTPS requests and handles HTTPS certificates and authentication through the AFSecurityPolicy class, but the execution of HTTPS requests is left to NSURLSession.

The following is a proxy method of NSURLSession that you can override and do your own custom authentication handling when certificate authentication is required. When the validation is complete, the result of the processing is informed by the block of the completionHandler, and disposition and public key credential of the validation result are passed in.

The AFN provides authentication logic through the AFSecurityPolicy class and manages certificates internally. Also can not use the AFN provide validation logic, rewrite sessionDidReceiveAuthenticationChallenge block can be custom validation logic, don’t walk AFN logic.

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void(^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling; }}else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; }}else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling; }}if(completionHandler) { completionHandler(disposition, credential); }}Copy the code

In addition to the callback for NSURLSession request validation, there are also proxy methods for each task. Two agents within a method implementation is essentially the same, the difference is that for each task, the AFN provides taskDidReceiveAuthenticationChallenge callback block, custom certificate validation process can be caused by the outside world.

Verification results is through an enumeration callback to NSURLSession, parameter is a NSURLSessionAuthChallengeDisposition type of enumeration, said certificate verification, this enumeration contains the following several specific values.

  • NSURLSessionAuthChallengeUseCredential established using the current certificateSSLConnect and process subsequent requests
  • NSURLSessionAuthChallengePerformDefaultHandling USES the default approach, the current certificate is ignored
  • NSURLSessionAuthChallengeCancelAuthenticationChallenge validation is not through, the request to cancel the whole network
  • NSURLSessionAuthChallengeRejectProtectionSpace the validation is ignored, but don’t cancel the network request

Security

Security-related processing, such as key management of HTTPS requests, is carried out in the security.framework. In AFSecurityPolicy, you often see variables of type SecTrustRef representing key objects that contain information such as public keys.

You can use the following command to obtain the public key. The specific format is not described here. You can Google the public key format for details.

// Get the public key command
SecTrustCopyPublicKey(serverTrust)

// Public key printed (public key desensitized)
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442D FBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B 976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5 ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43 608D6F494360FFDEE83AD1DCE120D6F1, addr:0x280396dc0>
Copy the code

AFSecurityPolicy overview

AFSecurityPolicy has a single responsibility, dealing only with public key and validation logic, and is defined as a singleton object. This class consists mainly of four attributes and a method.

// Certificate authentication mode
@property (readonly.nonatomic.assign) AFSSLPinningMode SSLPinningMode;
// Set of local self-signed certificates
@property (nonatomic.strong.nullable) NSSet <NSData *> *pinnedCertificates;
// Whether to validate the certificate (whether to allow self-signed certificates)
@property (nonatomic.assign) BOOL allowInvalidCertificates;
// Check whether the domain name is valid
@property (nonatomic.assign) BOOL validatesDomainName;
Copy the code

If you break it down, AFSecurityPolicy does basically two things. One is via CA and the other is SSL Pinning self-signed authentication. EvaluateServerTrust: forDomain: is one of the main AFSecurityPolicy method, is used to verified the validity of the certificate.

SSL Pinning

AFSecurityPolicy can do SSL Pinning in one of three ways. If it’s None, it will perform the normal CA validation process, and the other two are self-signed processes. The default call in AFN is the defaultPolicy method, which is internally set to AFSSLPinningModeNone mode.

  • AFSSLPinningModeNone Normal process, passCAPublic key issued by the organization, verifies the digital signature of the certificate issued by the server, and obtains the public key.
  • AFSSLPinningModeCertificate not throughCA, but through the local built-in server certificate, the verification process is divided into two steps. First, verify that the certificate is expired or invalid, and then verify that the certificate is locally contained.
  • AFSSLPinningModePublicKey withoutCAThe certificate is not verified, only the public key is valid.

The local self-signed certificate can be managed in two ways. One is that all the. Cer files are searched locally by default and stored in a collection of self-signed certificates. SSLPinningMode can also be passed when creating an AFSecurityPolicy object. Here is the logic for finding a local. Cer file.

+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@ "."];

    NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
    for (NSString *path in paths) {
        NSData *certificateData = [NSData dataWithContentsOfFile:path];
        [certificates addObject:certificateData];
    }

    return [NSSet setWithSet:certificates];
}
Copy the code

Self-signed certificate

During HTTPS handshake, the CA public key is used to verify the validity of the server public key and is not tampered with as a faulty public key. Using either NSURLSession or AFNetworking does not require any code changes if you use certificates issued by the CA authority; this is done automatically. If you do not want to use CA certificate authentication, for example, self-signed certificates will fail during CA certificate authentication.

In this case, you can use self-signed authentication, that is, a certificate is built-in on the client. During the four-way handshake, the server compares the local certificate with the server. If the certificates are the same, the authentication succeeds.

AFN provides a verification method for self-signed certificates. SSLPinningMode is used to set the verification mode to self-signed, and the certificate set is passed in. If no set of certificates is passed in, the AFN defaults to traversing the entire sandbox looking for all. Cer certificates.

To sandbox validation, you need to set AFSecurityPolicy’s allowInvalidCertificates to YES. The default is NO, which allows invalid certificates, that is, self-signed certificates.

NSString *cerPath = [[NSBundle mainBundle] pathForResource:@ "12306" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;
Copy the code

AFNetworking 2.x

Afnetworking2. x consists of NSURLSession and NSURLConnection, and corresponds to different classes respectively. Here we mainly introduce the source code implementation of NSURLConnection.

General overview

NSURLConnection is made up of the following three classes. It can be seen from the source code that both session and Connection schemes have good expansibility. For example, AFHTTPRequestOperation is implemented based on AFURLConnectionOperation. If FTP protocol needs to be implemented, You can create an AFFPTConnectionOperation class that inherits from AFURLConnectionOperation and rewrite the corresponding method.

  • AFURLConnectionOperation inherited fromNSOperationIs responsible for the logical implementation of network requests, one for each network requestOperationObject.
  • AFHTTPRequestOperation inherited fromAFURLConnectionOperationTo deal withHTTPRelated network request.
  • Hold a AFHTTPRequestOperationManager insideNSOperationQueueIs responsible for managing allOperationNetwork request.

AFURLConnectionOperation

Here is the initialization method for AFURLConnectionOperation, which is a little different from afurlssession Manager. It adds the concept of state and the concept of RunloopMode, which we’ll cover in more detail later. Whether shouldUseCredentialStorage said made certificate authentication system, set up behind the securityPolicy, as well as our sessionManager is using the default scheme.

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    _state = AFOperationReadyState;

    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    self.shouldUseCredentialStorage = YES;
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
}
Copy the code

AFURLConnectionOperation inherits from NSOperation. Since NSOperation is similar to the process of network request, including start, pause, finish, etc., and supports KVO listening well, AFN regards each network request as an Operation task. AFURLConnectionOperation can set the task priority, but can be by AFHTTPRequestOperationManager set the maximum number of concurrent, basically NSOperationQueue function can provide.

Defined in AFHTTPRequestOperationManager NSOperationQueue, create network request task, will be added to the queue, then the system calls the operation task in the queue, Execute the start method of operation to initiate the request. AFURLConnectionOperation only needs to implement the parent class methods such as start, Pause, and resume internally. Other methods are called by the system. This design can be well decoupled from the manager and operation, the two do not have a direct call relationship.

In NSURLConnection, each network request corresponds to an AFHTTPRequestOperation. All network requests share a manager to manage the operation. AFHTTPSessionManager, on the other hand, has a manager and a task for each network request.

The state design

AFURLConnectionOperation supports a KVO approach that lets the outside world listen for changes in network requests and trigger a KVO callback by overriding the setState method and adding willChangeValueForKey internally. AFN manages network request state through AFOperationState. The following is AFN’s definition of its state.

  • AFOperationPausedState Request pause
  • The AFOperationReadyState request is ready
  • AFOperationExecutingState request is in execution
  • AFOperationFinishedState The request completed

When the state of the network request changes, the setState method is called to assign the value. For example, the following is the processing code when the request completes. In addition, this attribute is also used to determine the status of AFN requests.

- (void)finish {
    [self.lock lock];
    self.state = AFOperationFinishedState;
    [self.lock unlock];
}
Copy the code

Permanent thread

In AFURLConnectionOperation, the resident thread is designed, and the operation start method is overwritten. The network request start, cancel, pause and other operations are completed in the resident thread. After the network request completes, the processing of the data callback is also done in this thread.

This is because the thread that created the NSURLConnection object and made the request also receives data from that thread by default when the data is returned. If all requests are made from the main thread and the screen is sliding when the request is returned, runloopMode is UITrackingRunLoopMode will not process the returned data. If network requests are added to the NSRunLoopCommonModes of the main thread, processing the returned data will affect the screen slide FPS when a large number of network requests return.

So in order to ensure that the network request data can be returned and processed normally without affecting the screen FPS, a separate thread is used to process it. If each request corresponds to a thread to process the return task, a large number of threads are occupied, so a resident thread is used to process all network requests to keep thread resource usage to a minimum. The resident thread is actually a singleton thread, and the singleton thread is added to a Port to keep the thread from exiting.

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool{[[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }} + (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}
Copy the code

The code for actually creating a Connection object when making a network request through AFURLConnectionOperation is in the method below. Instead of making a network request immediately after the Connection object is created, startImmediately is set to NO. The runLoopModes of NSURLConnection and NSOutputStream are then set, and the network request is issued from the runLoopModes of the singleton so that when the network request returns, the callback code is executed in the runLoopModes.

OperationDidStart method will be called NSURLConnection scheduleInRunLoop: forMode: methods, distributed network requests to specified Runloop Mode. I don’t think it makes much sense to set runLoopModes for Operation, because resident threads usually have only one Mode, NSRunloopDefaultMode, and no other Mode.

- (void)operationDidStart {
    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    for (NSString *runLoopMode in self.runLoopModes) {
        [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
        [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
    }

    [self.outputStream open];
    [self.connection start];
}
Copy the code

Proxy method

AFURLConnectionOperation implements network requests through NSURLConnection. Here is a brief introduction to the implementation of proxy methods in operation.

AFN implements HTTPS certificate verification code. The specific implementation is similar to AFURLSessionManager, and AFSecurityPolicy processes the specific certificate verification logic.

- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
Copy the code

It is worth mentioning that when NSURLConnection receives server data, AFN creates an outputStream to host and organize the specific data and store it in memory. When no space is available or other errors occur, they are represented in streamError.

When the network request ends, the didFinishLoading method is called, and the AFN takes data from outputStream and assigns it to responseData as return value data.

- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        if ([self.outputStream hasSpaceAvailable]) {
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                if (numberOfBytesWritten == - 1) {
                    break;
                }

                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        } else{[self.connection cancel];
            if (self.outputStream.streamError) {
                [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            }
            return; }}if (self.downloadProgress) {
        self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); }} - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    [self.outputStream close];
    if (self.responseData) {
       self.outputStream = nil;
    }

    self.connection = nil;

    [self finish];
}
Copy the code

There is an outputStream, and there is an inputStream. The implementation of inputStream is simply to modify the HTTPBodyStream of NSMutableURLRequest.

- (NSInputStream *)inputStream {
    return self.request.HTTPBodyStream;
}

- (void)setInputStream:(NSInputStream *)inputStream {
    NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
    mutableRequest.HTTPBodyStream = inputStream;
    self.request = mutableRequest;
}
Copy the code

Memory management

Blocks for success and failure are passed to operation when AFHTTPRequestOperation is created, and both blocks are executed when operation completes and the completionBlock is called back. But because self is used directly within the completionBlock, it causes the problem of circular references.

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
      self.completionBlock = ^{
          // something code ...
      };
}
Copy the code

The circular reference to the completionBlock is intentional by the AFN to maintain the operation lifecycle to ensure that the request is processed and the block callback returned is received.

For the life cycle of a circular reference, AFN takes the approach of actively breaking a circular reference by overwriting the completionBlock of the parent class and actively breaking a circular reference by assigning the completionBlock to nil after the call to the block ends.

- (void)setCompletionBlock:(void(^) (void))block {
    if(! block) { [super setCompletionBlock:nil];
    } else{__weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop

            dispatch_group_async(group, queue, ^{
                block();
            });

            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil]; }); }]; }}Copy the code

AFNetworkReachabilityManager

Reachability is an important part of AFNetworking. It is used to monitor network status. AFNetworking, YYKit and Apple all officially provide apis for Reachability, and the internal implementation principles are basically the same.

Code implementation is simple, mainly depends on SystemConfiguration framework framework SCNetworkReachability, register a Callback and then waiting for the Callback can. Here’s the core logic, and I’m going to ignore some of the details.

Reachability provides two initialization methods. The managerForDomain: method is used to initialize a domain name and determine the current network status based on the access to the domain name. The other is to create a sockaddr_in object based on the address-initialized managerForAddress: method to determine the network state.

+ (instancetype)managerForAddress:(const void *)address {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    CFRelease(reachability);
    
    return manager;
}

+ (instancetype)manager {
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
    return [self managerForAddress:&address];
}
Copy the code

The following startMonitoring is the core code to enable network monitoring. The main logic is to set up two Callback, one is a block and one is a function, and add it to the Runloop to startMonitoring. It can be inferred from this that the code implementation of Reachability mainly relies on the event loop of Runloop and determines the network state in the event loop.

When the network is changed, will the callback AFNetworkReachabilityCallback function, the callback has three parameters. Target is the SCNetworkReachabilityRef object, flags is the network state, and info is the block callback argument we set. After the Callback function is called, the internal Callback is issued in the form of blocks and notifications.

- (void)startMonitoring {
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if(strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); }};SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); }}); } - (void)stopMonitoring {
    if (!self.networkReachability) {
        return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
Copy the code

AFNetworking summary

AFNetworking does a lot to serialize the requested data, as well as the deserialization of the returned data. This allows the developer to simply pass in a dictionary to build the request parameters without having to deal with the details of concatenating the parameters to the URL or converting them to the body binary. This is handled internally and fault-tolerant by AFNetworking.

AFSecurityPolicy can manage THE CA and self-signed certificates, as well as some logic in the process of processing HTTPS requests. We just need to tell AFNetworking how to handle it. If the processing mode is not specified, the default CA certificate is used.

AFNetworking has good support for background download and breakpoint continuation. We can design a download module very easily based on AFNetworking. If you write background download and breakpoint continuation of the code, the workload is not small.

AFNetworking also provides strong customization in the design of the network library, such as specifying certificates, URL caching, and processing at different download stages in the download process. If no custom processing is provided, the default processing is used.