The Demo address

Github.com/tanghaitao/…

AFN general framework

The general idea

Url — Request (Configureation)– Task — Session — Agent — Manager — Client — RAC

This chapter mainly analyzesNSURLSessionPart of the

Demo address in this chapter:

Github.com/tanghaitao/…

NSURLSession process

@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>

AFHTTPSessionManager mainly consists of

Requests such as Get and Post

AFURLSessionManager mainly consists of

Proxy request, callback and other task execution process

// This is an initialization, not a singleton, AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; return [[[self class] alloc] initWithBaseURL:nil];Copy the code

Initialize NSURLSession using configuration

Call the parent class initialization method, self = [super initWithSessionConfiguration: configuration];

/ * 1. Initialization of a session (2) to the manager of the property is set the initial value * / - (instancetype) initWithSessionConfiguration: (NSURLSessionConfiguration *)configuration { self = [super init]; if (! self) { return nil; } // Set the default configuration to configure our session if (! configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } / / hold the configuration self. SessionConfiguration = configuration; // Create session serial: Multiple tasks, callback is asynchronous // set as delegate operationQueue number of concurrent threads 1, i.e. serial queue self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; // Block coarse data: only response,data,error // Delegate: block, status code (cancel), progress bar and other data are returned. /* - If you need to do complex (time-consuming) processing after completion, you can choose asynchronous queue - if you need to update the UI directly after completion, Can choose the home side columns [NSOperationQueue mainQueue] * / self. The session = [NSURLSession sessionWithConfiguration: self. SessionConfiguration delegate:self delegateQueue:self.operationQueue]; Self. ResponseSerializer = [AFJSONResponseSerializer]; // Set default certificate Unconditional certificate HTTPS authentication self.securityPolicy = [AFSecurityPolicy defaultPolicy]; // Set default certificate unconditional certificate HTTPS authentication self.securityPolicy = [AFSecurityPolicy defaultPolicy]; #if ! TARGET_OS_WATCH / / network status monitoring self. ReachabilityManager = [AFNetworkReachabilityManager sharedManager]; #endif // why collect: cancel resume supend: task: id //delegate= value taskid = key self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; // use NSLock to ensure thread safety self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; // Asynchronously retrieves all pending tasks of the current session. In fact, it makes sense that when you call this method in initialization, there should be no task that comes back and initializes the session, May have a previous task / / https://github.com/AFNetworking/AFNetworking/issues/3499 / / [self. The session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil]; } for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil]; } }]; return self; }Copy the code

HTTPS flow chart and detailed analysis

  1. The client sends a request URL based onhttpsHTTPS is equivalent to the HTTP + SSL encryption policy
  2. Server side validation hasPublic and private keys
  3. Through the serverPublic key Sets the encryption algorithmRespond to the client
  4. The client willCRT receivedLocal CRTCompare (A local CA certificate or apple CA certificate provided by the background), and generates one after verificationRandom number Random keyIn theThe public keyEncrypted save in
  5. Will thisEncrypted public keySend to the server
  6. Through the serverThe private keyunlockEncrypted public keyTo get the clientRandom number Random key, through this keyencryptionAll the data to be passed, such as Response, name,token, etc
  7. This data is sent to the client
  8. Client passRandom number Random key decryption

That’s how HTTPS works.

Request parameter handling

- (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 { // http: Request line + request header + request body // Multi-threaded task? // Return a task, NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; // Start the network request [dataTask resume]; return dataTask; }Copy the code
//1. Generate request, 2. Create task from request - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(void (^)(NSURLSessionDataTask *, id))success failure:(void (^)(NSURLSessionDataTask *, NSError *))failure { NSError *serializationError = nil; Call AFHTTPRequestSerializer requestWithMethod to build Request 2. //relativeToURL concatenates URLString to baseURL */ NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];Copy the code

here

[self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]

GET header content-type does not set POST header content-type default is Application/X-www-form-urlencoded Example if the POST request is set, For the application/json / / set 1 manager. RequestSerializer = [AFJSONRequestSerializer serializer]; / / set 2 [manager requestSerializer setValue: @ "application/json forHTTPHeaderField:" @ "content-type"].Copy the code
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager GET:urlStr parameters:dic progress:^(NSProgress * _Nonnull downloadProgress) {

po request.allHTTPHeaderFields
{
    "Accept-Language" = "en;q=1";
    "User-Agent" = "afn_demo/1.0 (iPhone; iOS 14.4; Scale/3.00)";
}
Copy the code

The default requestSerializer for POST is the AFHTTPRequestSerializer class.

self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
Copy the code
if (! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; }Copy the code

If one of the following two items is set,

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; / / set 1 manager. RequestSerializer = [AFJSONRequestSerializer serializer]; / / set 2 [manager requestSerializer setValue: @ "application/json forHTTPHeaderField:" @ "content-type"]. [manager POST:urlStr parameters:dict progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {Copy the code

Perform set 1 manager. RequestSerializer = [AFJSONRequestSerializer serializer] : recommended way, json processing

po self.requestSerializer <AFJSONRequestSerializer: 0x600003b2b3c0> if (! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]]; po parameters { username = ( { age = 18; name = Cooci; }, { age = 19; name = Gavin; }); } Default format is JSONCopy the code

Perform set 2 [manager requestSerializer setValue: @ “application/json” forHTTPHeaderField: @ “content-type”].

po self.requestSerializer <AFHTTPRequestSerializer: 0x600000c8c300> if (! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; Query is the username % 5 b % 5 d % 5 bage % 5 d = 18 & username % 5 b % 5 d % 5 bname % 5 d = CoociCopy the code
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
Copy the code

For 127.0.0.1:8080, you can use WireShakes to grab local LoopBack (LO0)

/ / by default, dic - count = 5 & start = 1 switch (self. QueryStringSerializationStyle) {case AFHTTPRequestQueryStringDefaultStyle: / / the parameters into the c functions query = AFQueryStringFromParameters (parameters); break; } // Internal method: C: OC-->C NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray  array]; / / / / pass parameters to AFQueryStringPairsFromDictionary couple, // username = @"haitao" for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary (parameters)) {/ / percent coding [mutablePairs addObject: [pair URLEncodedStringValue]]. } / / split array parameter string return [mutablePairs componentsJoinedByString: @ "&"); } //query:ab%5Bco%5D=sd&ab%5Bhello%5D=123&ie%5B%5D=4&ie%5B%5D=2&ie%5B%5D=3&ie%5B%5D=1&jb=jc&rb=rb&tn=monline_3_dg&wd=%E4%B Count =5*# ASCII uinicode count=5* / - (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
/ / the last judgment in the request contains a GET, HEAD, and DELETE (are included in the HTTPMethodsEncodingParametersInURI). This is because the methods' queries are concatenated to the url. POST and PUT concatenate query into the HTTP body. if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query && query.length > 0) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { // #2864: an empty string is a valid x-www-form-urlencoded payload if (! query) { query = @""; } // The content-type function checks whether the request's content-type is set, and if not, Application /x-www-form-urlencoded // Application /x-www-form-urlencoded is a common form submission method, and js will be sent in this way by default if (! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } / / set the request body [mutableRequest setHTTPBody: [query dataUsingEncoding: self. StringEncoding]]. }Copy the code

Complex data processing bug

AFN request parameter percentile encoding encapsulation

// use recursion /* Then check the type of value, NSDictionary, NSArray, NSSet. But someone will ask, for AFQueryStringPairsFromKeyAndValue function is introduced to a value not in AFQueryStringPairsFromDictionary NSDictionary? Why do we have to judge so many types? Right, ask very well, this is the core - AFQueryStringPairsFromKeyAndValue recursive calls and parse, you can't guarantee the NSDictionary value is stored in an NSArray, NSSet. */ */ key=count valus=5 NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {//key=count value = 5 NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; /* Perform ascending order based on the description of the object to be sorted, and selector uses compare: Because the description of the object returns an NSString, So compare: is using the NSString compare function */ NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; // Determine what type vaLue is, then recursively call yourself until you parse elements other than array DIC set, then return the resulting array of parameters. if ([value isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = value; // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries /* allkeys:count/start */ for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[sortDescriptor ]]) { id nestedValue = dictionary[nestedKey]; //nestedkey=count nestedvalue=5 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) {// Data has no index problem. // [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%lu]", key,(unsigned long)[array indexOfObject:nestedValue]], nestedValue)]; [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 {// If the value is an NSString, then call the last else statement in the function, //AFQueryStringPair, Is a type of elements in mutableQueryStringComponents AFQueryStringPair [mutableQueryStringComponents addObject: [[AFQueryStringPair alloc] initWithField:key value:value]]; } return mutableQueryStringComponents; }Copy the code

There’s a problem with an AFN where values are arrays,

Nsstrings * urlStr = @ "http://127.0.0.1:8080/postMethod/array"; NSDictionary *dict = @{@"username": @[ @{ @"name":@"Cooci", @"age":@18 }, @{ @"name":@"Gavin", @"age":@19 } ] };Copy the code

The key of the element is the same

ie%5B%5D=4&ie%5B%5D=2&ie%5B%5D=3&ie%5B%5D=1
Copy the code

Solution:

1. Modify the third-party code to manually add the array index

// There is no index problem with data. //[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%lu]", key,(unsigned long)[array indexOfObject:nestedValue]], nestedValue)];Copy the code

2. Convert an array to a JSON string. There is no index problem

NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&parseError];
NSString *jsonstr =[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
jsonstr = [jsonstr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
jsonstr = [jsonstr stringByReplacingOccurrencesOfString:@" " withString:@""];
jsonstr = [jsonstr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSDictionary *dict2 = @{@"username":jsonstr};
Copy the code

The task and the delegate

We have initialized NSURLSession and UIRequest through the above steps. The next step is to initialize the Task via Request

Request line + request header + request body // Return a task and start the network requestCopy the code

When multiple repeated requests come in at the same time, ensure that the callback is not corrupted, synchronize serial, and the second request cannot be executed until the first request is completed.

__block NSURLSessionDataTask *dataTask = nil; Url_session_manager_create_task_safely (^{// Native method // use session to create an NSURLSessionDataTask object dataTask = [self.session dataTaskWithRequest:request]; }); // Task and block do not match // Taskid should be unique, concurrent task creation, Id not only static void url_session_manager_create_task_safely (dispatch_block_t block) {NSLog (@ "NSFoundationVersionNumber = %f",NSFoundationVersionNumber); if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { // Fix of bug // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 dispatch_sync(url_session_manager_creation_queue(), block); } else {block(); }}Copy the code

The core code

/ / initialize the delegate AFURLSessionManagerTaskDelegate * delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; / / function will literally means a session of task and a delegate AFURLSessionManagerTaskDelegate type variables together, which tied together work is done by our AFURLSessionManager. As for the binding process, the taskIdentifier of the session task is the key, the delegate is the value, Assigned to the NSMutableDictionary mutableTaskDelegatesKeyedByTaskIdentifier types of variables. Knowing that the two are connected immediately raises another question -- why and how? [self setDelegate:delegate forTask:dataTask];Copy the code
/ / as the task Settings associated delegate - (void) setDelegate: (AFURLSessionManagerTaskDelegate *) delegate forTask task: (NSURLSessionTask *) { // Neither task nor delegate can be null NSParameterAssert(Task); NSParameterAssert(delegate); [self.lock lock]; // Store the delegate in the dictionary with taskid as the key, Instructions for each task has its own agent self. The mutableTaskDelegatesKeyedByTaskIdentifier [@ (task. TaskIdentifier)] = delegate; / / set two variables NSProgress - uploadProgress and downloadProgress, give the session task added two KVO events [delegate setupProgressForTask: task]; [self addNotificationObserverForTask:task]; [self.lock unlock]; }Copy the code

The delegate holds the Afhttpssession Manager and the external request callback

Task to hold the delegate

// Store the delegate in the dictionary with taskid as the key, Instructions for each task has its own agent self. The mutableTaskDelegatesKeyedByTaskIdentifier [@ (task. TaskIdentifier)] = delegate;Copy the code
@interface AFURLSessionManagerTaskDelegate : NSObject < NSURLSessionTaskDelegate NSURLSessionDataDelegate, NSURLSessionDownloadDelegate > / / weak prevent circular references (hold the task manager, @property (nonatomic, weak) AFURLSessionManager *manager;Copy the code

Break the loop reference to recycle

The task manager.

NSURLSessionTaskDelegate callback NSURLSessionTaskDelegate callback

// callback method, - (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask DidReceiveData :(NSData *)data {// splice data // data stream memory? NSLog(@"delegate--%@",[NSThread currentThread]); //task.id = deleagte // request home not end VS request detail? // deleaget.mutableData --> task.id // managr --> session: task [self.mutableData appendData:data]; } - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{/Performance Improvement from #2672 // See #issue 2672. So this is mostly for large files, the performance improvement is pretty noticeable NSData *data = nil; if (self.mutableData) { data = [self.mutableData copy]; //We no longer need the reference, so nil it out to gain back some memory. self.mutableData = nil; }Copy the code

Once the data is received, the concatenation, didCompleteWithError is done, if the data exists, a temporary variable is saved and returned, and the mutableData is set to null, so the property doesn’t keep growing.

/ / 1: home: task task is created, according to the url created msmutablerequest dataTask = [self. The session dataTaskWithRequest: request]; //2: create a temporary variable: task.id = delegate // detail: task //2: Create another temporary variable task.id = delegate // store delegate in the dictionary with taskid as the key, Instructions for each task has its own agent self. The mutableTaskDelegatesKeyedByTaskIdentifier [@ (task. TaskIdentifier)] = delegate;Copy the code

Self. MutableData where self is a delegate. The delegate is different, so it doesn’t mess up.

Attached is Xcode to configure the Python environment: blog.csdn.net/qq_15289761…