Write at the beginning:
  • As an iOS developer, you may not know about NSUrlRequest, NSUrlConnection, or NSURLSession… (Can’t go on… How come you don’t know anything…) But you know AFNetworking.
  • Most people are used to using AF whenever they request a network, but do you really know what AF does? Why do we choose AFNetworking instead of native NSURLSession?
  • This paper will analyze the actual role of AF from the perspective of source code. Perhaps after reading this article, you will have an answer in mind.
Let’s start with the latest AF3.x:
  • First, let’s examine the composition of the framework.

    After downloading AF into the project, here is its package structure, which is very simple compared to 2.x:




    AF code structure diagram. PNG

Apart from the Support Files, it can be seen that AF is divided into the following five functional modules:

  • Network communication module (AFURLSessionManager, AFHTTPSessionManger)
  • Reachability
  • Network Communication Security Policy Module (Security)
  • Serialization module for network communication information
  • Extensions to the iOS UIKit library (UIKit)
Its core of course is the network communication module NSURlssession Manager. As you all know, AF3.x is encapsulated based on NSURLSession. So this class does a bunch of wrapping around NSURLSession. The other four modules are for network communication or an extension to the existing UIKit.

The structure diagram of the classes corresponding to these five modules is as follows:




AF architecture diagram. PNG

AFHTTPSessionManager is inherited from NSURLSessionManager. We generally use AFHTTPSessionManager to make network requests, but it does not do practical things. Just distribute some of the request logic to the parent class AFURLSessionManager or some other class to do it.

First we simply write a get request:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];

[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

}];Copy the code

First we call the initialization method to generate a manager. Let’s click on it to see what the initialization does:

- (instancetype)init { return [self initWithBaseURL:nil]; } - (instancetype)initWithBaseURL:(NSURL *)url { return [self initWithBaseURL:url sessionConfiguration:nil]; } - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { return [self initWithBaseURL:nil sessionConfiguration:configuration]; } - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super initWithSessionConfiguration:configuration]; if (! self) { return nil; If ([[url path] length] > 0 &&! [[url absoluteString] hasSuffix:@"/"]) { url = [url URLByAppendingPathComponent:@""]; } self.baseURL = url; self.requestSerializer = [AFHTTPRequestSerializer serializer]; self.responseSerializer = [AFJSONResponseSerializer serializer]; return self; }Copy the code
  • Initialization is called- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configurationMethod comes in.
  • In fact, the initializer method calls the parent class’s initializer method. The parent class is AFHTTPSessionManager, the most core af3. x class. Almost all classes handle business logic around this class.
  • In addition, baseURL is stored in the method, and a request sequence object and a response sequence object are generated. More on what these two classes are for later.

Go straight to the parent AFURLSessionManager initialization method:

- (instancetype)init { return [self initWithSessionConfiguration:nil]; } - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (! self) { return nil; } if (! configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self.sessionConfiguration = configuration; self.operationQueue = [[NSOperationQueue alloc] init]; / / the queue number of concurrent threads is set to 1 self. OperationQueue. MaxConcurrentOperationCount = 1; // Pay attention to proxy, proxy inheritance, in fact, NSURLSession to determine which methods you implement will be called, including subproxy methods! self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; Self. ResponseSerializer = [AFJSONResponseSerializer]; // set the default securityPolicy self.securitypolicy = [AFSecurityPolicy defaultPolicy]; #if ! TARGET_OS_WATCH self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; # endif / / set the storage NSURL task with AFURLSessionManagerTaskDelegate dictionary (key, in AFNet, Each task will be to match a AFURLSessionManagerTaskDelegate delegate event handling to do the task) = = = = = = = = = = = = = = = self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; / / set AFURLSessionManagerTaskDelegate dictionary of locks, to ensure the safety of the dictionary in multithreaded access thread self. Lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; / / empty task associated agents [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
  • This is the final initialization method, the comments should be very clear, the only things that need to be said are three:
    • self.operationQueue.maxConcurrentOperationCount = 1;The operationQueue is the queue that we call back by proxy. The number of concurrent threads for the proxy callback is set to 1.As to why we’re doing this, let’s leave a hole and analyze this piece after we finish af2.x.
    • The second is that we initialize some properties, includingself.mutableTaskDelegatesKeyedByTaskIdentifierIn fact, AF encapsulates the agent of task and forwards the agent to the agent defined by AF. This is an important part of AF, and we will talk about it in detail next.
    • The third is the following method:
      [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
      }];Copy the code

      This method is used to asynchronously retrieve all pending tasks in the current session. In fact, it is reasonable to call this method in the initialization should not contain a task. Let’s interrupt and see, and it’s true, the array inside is empty. But if you think about it, AF wouldn’t put a piece of useless code in there. So I would hazard a guess that when we repeatedly initialize sessions (which we don’t actually do), there will be new sessions pointing to the old session with unfinished tasks. So to get rid of that uncertainty, we set all the bound proxies and stuff to nil at initialization. perhapsThis is a kind of defensive programming idea.

The initialization method is all done at this point.




Segmentation image. PNG

Then let’s look at network requests:

- (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void (^)(NSProgress * _Nonnull))downloadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, // Create 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

The AFHTTPSessionManager method calls the parent class, which is our core af3.x class AFURLSessionManager, generates an instance of the system’s NSURLSessionDataTask, and starts the network request. Let’s move on to the parent class and see what this method does:

- (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; // Put the parameter, There are various things that can be converted into a request NSMutableURLRequest * Request = [self.requestSerializer requestWithMethod: Method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" Self.com pletionQueue?: dispatch_get_main_queue(), ^{failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } __block NSURLSessionDataTask *dataTask = nil; dataTask = [self dataTaskWithRequest:request uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error);  } } else { if (success) { success(dataTask, responseObject); } } }]; return dataTask; }Copy the code
  • This method does two things: 1. It uses self.requestSerializer and various parameters to get an instance of NSMutableURLRequest that we need for our final request. 2. Call another method, dataTaskWithRequest, to get the NSURLSessionDataTask instance we finally need, and in the completed callback, call the success and failure callbacks we passed.
  • Note the following method, which we use to push pop to ignore some compiler warnings:
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
    #pragma clang diagnostic popCopy the code

    This is used to ignore😕For a detailed description of the various compiler warnings, see this article:Various compiler warnings.

  • After all, the requestSerializer method doesn’t work, so let’s go to the requestSerializer method to see how the AF splashes together the request we need:

Then we ran to the AFURLRequestSerialization class:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters Error :(NSError *__autoreleasing *)error {// in debug mode, crash NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; NSParameterAssert(url); NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; / / will request various attributes to iterate over the for (nsstrings * keyPath AFHTTPRequestSerializerObservedKeyPaths in ()) {/ / if they observed the changes of the properties of the In these methods the if ([self mutableObservedChangedKeyPaths containsObject: keyPath]) {/ / set the attribute to set themselves up to request [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; }} // Encode the parameters passed in, To add to the request of mutableRequest = [[self requestBySerializingRequest: mutableRequest withParameters: the parameters error: error] mutableCopy]; return mutableRequest; }Copy the code
  • This method does three things: 1) Set the request type of the request, get, POST,put… 2) in the request to add some parameters, such as setting, including AFHTTPRequestSerializerObservedKeyPaths () is a c function that returns an array, we take a look at this function:

    static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; // The observer's keypath is required to be allowsCellularAccess, cachePolicy, HTTPShouldHandleCookies HTTPShouldUsePipelining, networkServiceType, timeoutInterval Dispatch_once (&onceToken, ^{ _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); / / is installed in many ways an array name, return _AFHTTPRequestSerializerObservedKeyPaths; }Copy the code

    This function encapsulates the names of the properties of NSUrlRequest. Take a look at the self mutableObservedChangedKeyPaths, this is an attribute of the current class:

    @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;Copy the code

    This collection is initialized in the -init method and a KVO listener is added to the NSUrlRequest attributes of the current class:

    / / reset every time the self change mutableObservedChangedKeyPaths = [NSMutableSet set]; // Add observers to these methods, namely, the various attributes of the request, Set methods for (nsstrings * keyPath AFHTTPRequestSerializerObservedKeyPaths in ()) {if ([the self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; }}Copy the code

    KVO trigger method:

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change Context :(void *)context {// when the set methods are observed to be called and not Null, they are added to the collection, Or remove the if (context = = AFHTTPRequestSerializerObserverContext) {if ([change [NSKeyValueChangeNewKey] isEqual: [NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; }}}Copy the code

    . At this point we know that the self mutableObservedChangedKeyPaths is actually our own request set collection of attribute values. Next call:

    [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];Copy the code

    Use KVC to set the property values to the request we requested.

    3) Encode the parameters to be passed and set them to the request:

    // Encode the parameters passed in, To add to the request of mutableRequest = [[self requestBySerializingRequest: mutableRequest withParameters: the parameters error: error] mutableCopy];Copy the code
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; // Go through my head, If there is a value is set to the request of the head [self. HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock: ^ (id field, id value, BOOL * __unused stop) { if (! [request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; // To convert various types of arguments, array dic set to string, to request NSString *query = nil; If (parameters) {/ / custom analytic way the if (self. QueryStringSerialization) {NSError * serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; }} else {/ / the default resolution mode switch (self. QueryStringSerializationStyle) {case AFHTTPRequestQueryStringDefaultStyle: query = AFQueryStringFromParameters(parameters); break; }}} / / the last judgment whether the request contains a GET, HEAD, and DELETE (are included in the HTTPMethodsEncodingParametersInURI). Because the quey of these methods is 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]]; // #2864: an empty string is a valid x-www-form-urlencoded payload if (! query) { query = @""; } 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]]. } return mutableRequest; }Copy the code

    This method does three things: 1. Get the parameters from self.HTTPRequestHeaders and assign 2 to the request. Convert the parameters of the requested network from the container types such as array DIC set to strings. For specific transcoding, we can use the self-defined method or the default transcoding method of AF. There’s nothing to be said for customizing, and it’s up to you how you want to parse it. We can look at the default:

    NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; / / give AFQueryStringPairsFromDictionary parameters, get AF a type of data is a key, value object, in URLEncodedStringValue splicing keyValue, A to the array for (AFQueryStringPair * pair in AFQueryStringPairsFromDictionary (parameters)) {[mutablePairs addObject: [pair URLEncodedStringValue]]; } / / split array parameter string return [mutablePairs componentsJoinedByString: @ "&"); } NSArray * AFQueryStringPairsFromDictionary (NSDictionary * dictionary) {/ / to cut with the return AFQueryStringPairsFromKeyAndValue(nil, dictionary); } NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; // Perform ascending order according to the description of the object to be sorted, and selector uses compare: // Because the object's description returns NSString, So here compare: use nsstrings compare function / / @ that [@ "foo" @ "bar", @ "bae"] -- -- -- -- > @ [@ "bae", @"bar",@"foo"] 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, To the as an array of dictionaries / / get the for (id nestedKey [in the 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
    • Transcoding is mainly the above three functions, with annotations should also be easy to understand: it is mainly called recursivelyAFQueryStringPairsFromKeyAndValue. Determine what type vaLue is, recursively call yourself until you parse elements other than array DIC set, and return the resulting array of parameters.
    • There is an AFQueryStringPair object with only two properties and two methods:

      @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; - (instancetype)initWithField:(id)field value:(id)value { self = [super init]; if (! self) { return nil; } self.field = field; self.value = value; return self; } - (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 method is very simple, now we are also easy to understand the whole transcoding process, let’s take an example to comb out, is the following three steps:

      @{ 
       @"name" : @"bang", 
       @"phone": @{@"mobile": @"xx", @"home": @"xx"}, 
       @"families": @[@"father", @"mother"], 
       @"nums": [NSSet setWithObjects:@"1", @"2", nil] 
      } 
      -> 
      @[ 
       field: @"name", value: @"bang", 
       field: @"phone[mobile]", value: @"xx", 
       field: @"phone[home]", value: @"xx", 
       field: @"families[]", value: @"father", 
       field: @"families[]", value: @"mother", 
       field: @"nums", value: @"1", 
       field: @"nums", value: @"2", 
      ] 
      -> 
      name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2Copy the code

      At this point, the parameters of our original container type are now strings.

    The method then determines how the parameter string should be placed in the request based on the request type. If it is GET, HEAD, or DELETE, the parameter quey is 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]]; // #2864: an empty string is a valid x-www-form-urlencoded payload if (! query) { query = @""; } 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

    At this point, we’ve generated a request.




Segmentation image. PNG

Let’s go back to the AFHTTPSessionManager class and go back to this method:
- (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; // Put the parameter, There are various things that can be converted into a request NSMutableURLRequest * Request = [self.requestSerializer requestWithMethod: Method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" Self.com pletionQueue?: dispatch_get_main_queue(), ^{failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } __block NSURLSessionDataTask *dataTask = nil; dataTask = [self dataTaskWithRequest:request uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error);  } } else { if (success) { success(dataTask, responseObject); } } }]; return dataTask; }Copy the code

Come full circle and we’re back again.

  • Let’s read on: When the resolution fails, we call the fauler’s Block directly and it fails to return. Here is a self.pletionQueue, which is our own, and this is a GCD Queue if set then callback from this Queue, otherwise callback from the main Queue.

  • This Queue is actually quite useful, and we’ve used it before. Our company has its own data encryption and decryption parsing mode, so the data we call back does not want to be the main thread. We can set up this Queue, parse the data in the thread, and then call back to the main thread to refresh the UI.

Anyway, we then call the parent class’s task generation method and execute a success and failure callback. We then go to the parent class AFURLSessionManger (finally our core class…). :

- (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; // First thing, create NSURLSessionDataTask (Ios8 taskIdentifiers) // This is probably implemented because iOS 8.0 or later allows you to create multiple tasks concurrently, and does not sync well enough to make taskIdentifiers unique... This way did a serial processing url_session_manager_create_task_safely (^ {dataTask = [self. The session dataTaskWithRequest: request]; }); [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; }Copy the code
  • We noticed how simple this method was, so we called oneurl_session_manager_create_task_safely()The dataTask function passes in a Block, which is the iOS native dataTask method. In addition, aaddDelegateForDataTaskMethods.
  • Let’s go over here and look at this function:

    static void url_session_manager_create_task_safely(dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { // Fix of bug // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed iOS8) / / Issue in about:https://github.com/AFNetworking/AFNetworking/issues/2093 / / understand, why first in sync, because such as the main thread that is want here, When the dataTask is executed, the value will be returned, because the dataTask must be executed before the data is available. // Second, why use serial queues, because this is to prevent internal datatAskWithRequests from being created concurrently under ios8, which causes taskIdentifiers to be more than unique. Because taskIdentifiers are going to be used as Key delegates. dispatch_sync(url_session_manager_creation_queue(), block); } else { block(); } } static dispatch_queue_t url_session_manager_creation_queue() { static dispatch_queue_t af_url_session_manager_creation_queue; static dispatch_once_t onceToken; // Ensure that even in multithreaded environments, No other queue dispatch_once(&onceToken, ^{ af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); }); return af_url_session_manager_creation_queue; }Copy the code
    • The method is very simple, but the key is to understand the purpose: why don’t we just call it

      dataTask = [self.session dataTaskWithRequest:request];

      If we had to go all the way around, let’s go into the bug log,It turns out that in iOS8, when you’re creating a session, there’s an occasional case where the taskIdentifier property of the session is not uniqueAnd the taskIdentifier is the key we use to map the delegate later, so it must be unique.
    • The reason is that NSURLSession generates tasks internally and executes them concurrently using multiple threads. With this in mind, we can solve this problem by simply synchronizing serial tasks under iOS8 (see the comment if you still don’t understand the serial synchronization reason).
    • Off topic: A lot of students will complain about why SYNC is never used. See, it is useful, many things are not useless, but you just can’t think of how to use it.

We then read:

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];Copy the code

Call to:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc]  init]; / / AFURLSessionManagerTaskDelegate establishing relationships with AFURLSessionManager delegate. The manager = self; delegate.completionHandler = completionHandler; / / this taskDescriptionForSessionTasks used for sending and suspend notice is used, is to use this value to Post notice, To the corresponding dataTask. TaskDescription = self. TaskDescriptionForSessionTasks; // ***** set the AF delegate object to the dataTask [self setDelegate:delegate forTask:dataTask]; // Set the upload progress of the AF delegate and download the progress block. delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }Copy the code
  • To sum up:

    1) This method generates oneAFURLSessionManagerTaskDelegateThis is actually a custom proxy for AF. The parameters we requested were assigned to the agent of AF.

    2)delegate.manager = self;The agent uses the AFURLSessionManager class as a property, as we can see:
    @property (nonatomic, weak) AFURLSessionManager *manager;Copy the code

    This property is weakly referenced, so there is no problem with circular references.

    3) We called[self setDelegate:delegate forTask:dataTask];

Let’s go in and see what this method does:

- (void) setDelegate: (AFURLSessionManagerTaskDelegate *) delegate forTask task: (NSURLSessionTask *) {/ / assert that if there is no this parameter, Debug crash in this NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; // Place the AF delegate in a dictionary marked with a taskIdentifier (taskIdentifier is unique within the same NSURLSession) self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; / / set the task progress to monitor for AF delegate [delegate setupProgressForTask: task]; / / add the task start and suspension notice [self addNotificationObserverForTask: task]; [self.lock unlock]; }Copy the code
  • This method basically maps the AF agent to the Task in a predefined dictionary.
  • The reason for locking is that the dictionary property itself is mutable, which is not thread-safe. And our calls to these methods are, indeed, in a complex multithreaded environment, and we’ll talk more about threads later.
  • There is a[delegate setupProgressForTask:task];Let’s go to the method:
- (void)setupProgressForTask:(NSURLSessionTask *)task { __weak __typeof__(task) weakTask = task; Uploads and downloads / / to get the desired data size self. The uploadProgress. TotalUnitCount = task. CountOfBytesExpectedToSend; self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive; // Cancel suspend your progress bar. You can cancel... Task [self uploadProgress setCancellable: YES]; [self.uploadProgress setCancellationHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask cancel]; }]; [self.uploadProgress setPausable:YES]; [self.uploadProgress setPausingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask suspend]; }]; if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.uploadProgress setResumingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }]; } [self.downloadProgress setCancellable:YES]; [self.downloadProgress setCancellationHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask cancel]; }]; [self.downloadProgress setPausable:YES]; [self.downloadProgress setPausingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask suspend]; }]; if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) { [self.downloadProgress setResumingHandler:^{ __typeof__(weakTask) strongTask = weakTask; [strongTask resume]; }]; } / / observation task these properties [task addObserver: self forKeyPath: NSStringFromSelector (@ the selector (countOfBytesReceived)) options:NSKeyValueObservingOptionNew context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive)) options:NSKeyValueObservingOptionNew context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) options:NSKeyValueObservingOptionNew  context:NULL]; [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) options:NSKeyValueObservingOptionNew context:NULL]; / / watch progress these two properties [self. The downloadProgress addObserver: self forKeyPath: NSStringFromSelector (@ the selector (fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; [self.uploadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; }Copy the code
  • This method is also very simple and does several things: 1) Set some properties for downloadProgress and uploadProgress, and bind them to the task state of the task. Note that both of these are instance objects of NSProgress. In simple terms, this is a class introduced in iOS7 to manage progress, can start, pause, cancel, complete corresponding to the various state of the task, when progress is performing various operations, task will also trigger corresponding operations. 2) Add KVO listener to task and progress.

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *) context {/ / task if ([object isKindOfClass: [NSURLSessionTask class]] | | [object IsKindOfClass :[NSURLSessionDownloadTask Class]]) {// Assign a new value to the progress bar if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; }} // The assignment above triggers both, calling the block callback, Users get progress else if ([object isEqual: self, downloadProgress]) {if (self. DownloadProgressBlock) { self.downloadProgressBlock(object); } } else if ([object isEqual:self.uploadProgress]) { if (self.uploadProgressBlock) { self.uploadProgressBlock(object); }}}Copy the code
    • If the task fires KVO, it assigns progress to the task. If the task fires KVO, it assigns progress to the task. If the task fires KVO, it assigns progress to the taskdownloadProgressBlockanduploadProgressBlock. The main function is to allow progress to be delivered in real time.
    • It is mainly to observe the structure of writing code of Daishen, this decoupled programming idea, worthy of being daishen…

    • One more thing to note: our previous setProgress and KVO listener are both in our AF custom delegate. Every task has a delegate. So each task listens for these attributes, respectively, in its own AF agent. This might be a little confusing for some of you, but that’s okay. After the whole lecture, we will talk about the corresponding relations among manager, Task and AF custom agent in detail.

At this point our entire task processing is complete.




Segmentation image. PNG

Then the task starts requesting the network, remember from our initialization method:

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];Copy the code

We use AFUrlSessionManager as a delegate for all tasks. When we request the network, these proxies start calling:




PNG proxy for NSUrlSession

  • AFUrlSessionManager implements a large number of nSURLSession-related agents as shown in the figure above. (The order of the agents may be different.)

  • Only three of them are forwarded to AF’s custom delegate:




    AF Custom delegate.png

    This is what we said at the beginning. AFUrlSessionManager does some common processing for the large number of agents, and the three forwarded to the AF custom agent are responsible for calling back the corresponding data for each task.

NSURLSessionDelegate = NSURLSessionDelegate How can so many proxies respond to NSUrlSession? Let’s go to the class declaration file:

@protocol NSURLSessionDelegate 
@protocol NSURLSessionTaskDelegate 
@protocol NSURLSessionDataDelegate 
@protocol NSURLSessionDownloadDelegate 
@protocol NSURLSessionStreamDelegate Copy the code
  • We can see that these agents are inheritance relationships, while inNSURLSessionIn the implementation, as soon as this proxy is set up, it’s going to determine whether or not all of these proxiesrespondsToSelectorMethods in these proxies are called if they respond.
  • AF also overrides the respondto Selector method:

    - (BOOL) respondsToSelector: (SEL) the selector {/ / copy the selector method, this is a few methods in this class is implemented, but if the Block outside the assignment, it returns NO, equivalent to not true! if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) { return self.taskWillPerformHTTPRedirection ! = nil; } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) { return self.dataTaskDidReceiveResponse ! = nil; } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) { return self.dataTaskWillCacheResponse ! = nil; } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) { return self.didFinishEventsForBackgroundURLSession ! = nil; } return [[self class] instancesRespondToSelector:selector]; }Copy the code

    This way, our custom Block will not call back the proxy if it is not implemented. Since some of the proxies themselves only execute these custom blocks, there is no point in calling the proxy if none of the blocks are assigned. With that said, let’s take a look at some custom blocks for afurlssession Manager:

    @property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
    @property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
    @property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
    @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;
    @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;Copy the code

    There are also a bunch of set methods like this:

    - (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
      self.sessionDidBecomeInvalid = block;
    }Copy the code

    The method is the same, do not repeat paste space. Mainly talk about this design idea

    • The author declared these Block properties in a.m file with @property, and then overwrote the set method.
    • Then declare the set methods in.h:
      - (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;Copy the code

      Why go all the way around?This is for the convenience of our users, call the set method to set these blocks, you can clearly see the Block parameters and return values.The essence of god’s programming thought is reflected everywhere…

Let’s talk about what these proxy methods do (in order) :

NSURLSessionDelegate
// This proxy method is called when the current session has expired. / * if you use finishTasksAndInvalidate function to disable the session, the session will first finish the final task, and then call URLSession: didBecomeInvalidWithError: proxy approach, If you invalidate a session by calling the invalidateAndCancel method, the session will immediately call the proxy method above. */ - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error { if (self.sessionDidBecomeInvalid) { self.sessionDidBecomeInvalid(session, error); } [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session]; }Copy the code
  • Method call timing comment is very clear, just call our custom Block, and send an invalid notification, as for the purpose of this notification. Sorry, AF didn’t do anything with it, just sent… The goal is that users themselves can do something with this notification.
  • In fact, this is true of most AF notifications. Of course, there are also some notifications that AF can use in conjunction with some extensions to UIKit, which we’ll explore in a separate section later.
/ / 2, HTTPS authentication - (void) URLSession: (NSURLSession *) session didReceiveChallenge challenge: (NSURLAuthenticationChallenge *) completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential completionHandler * the credential)) {/ / challenges processing type as the default / * NSURLSessionAuthChallengePerformDefaultHandling: The default process NSURLSessionAuthChallengeUseCredential: using the specified certificate NSURLSessionAuthChallengeCancelAuthenticationChallenge: Cancel the challenge * / NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; / / sessionDidReceiveAuthenticationChallenge is a custom method, Server side authentication challenge to how to deal with the if (self. SessionDidReceiveAuthenticationChallenge) {disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else {/ / the server requires client receive certification is NSURLAuthenticationMethodServerTrust / / challenge method That is to say the server to the client returns a according to the authentication challenge to protect space for trust (namely challenge. ProtectionSpace. ServerTrust) have certificate of challenge. / / and the certificate will need to use the credentialForTrust: to create a NSURLCredential object if ([challenge. ProtectionSpace. AuthenticationMethod IsEqualToString: NSURLAuthenticationMethodServerTrust]) {/ / based on the client's security policies to decide whether to trust the server, distrust, Also no need to respond to challenges the if ([self. The securityPolicy evaluateServerTrust: challenge. ProtectionSpace. ServerTrust ForDomain: challenge. ProtectionSpace. Host]) {/ / create a challenge certificate (note: The challenge mode is UseCredential and PerformDefaultHandling both require a new challenge certificate) credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; / / determine the challenge if (the credential) {/ / certificates challenges disposition = NSURLSessionAuthChallengeUseCredential; } else {// The default challenge is only different, this step is missing! disposition = NSURLSessionAuthChallengePerformDefaultHandling; }} else {/ / cancel the challenge disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; }} else {/ / the default disposition = NSURLSessionAuthChallengePerformDefaultHandling challenge mode; }} // Complete the challenge if (completionHandler) {completionHandler(disposition, credential); }}Copy the code
  • Function functions:

    When a Web server receives a request from a client, it sometimes needs to verify that the client is a normal user before deciding whether to return real data. This situation is called service client request the client receives the challenge (NSURLAuthenticationChallengeChallenge). After receiving the challenge, the client according to the service side of the challenge to generate needed for the completionHandler NSURLSessionAuthChallengeDisposition disposition and NSURLCredentialThe credential (disposition to specify with method of the challenge, and challenge the credential is the client certificate, pay attention to the only challenge the authentication method for NSURLAuthenticationMethodServerTrust, Need to generate a challenge certificate). Finally, call completionHandler in response to the server-side challenge.
  • Function discussion:

    The proxy method is called in two cases:
    1. This method allows your app to provide the correct challenge certificate when the server asks the client to provide a certificate or for NTLM authentication (WindowsNT LAN Manager).
    2. When a session uses SSL/TLS to establish a connection with the server for the first time, the server sends a certificate to the iOS client. This method allows your app to verify the certificate keychain of the server. If you don’t have to implement this method, the session will be to call its NSURLSessionTaskDelegate agent method URLSession: task: didReceiveChallenge: completionHandler:.

Here, I have translated the official documentation describing this method. To summarize, this method is actually doing HTTPS authentication. If there is a custom authentication Block, we will call our custom authentication Block, otherwise we will execute the default authentication step, and finally we will call the authentication step:

// Complete the challenge if (completionHandler) {completionHandler(disposition, credential); }Copy the code

Moving on:

//3. This proxy method is called when all the queued messages in the session have been sent. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (self.didFinishEventsForBackgroundURLSession) { dispatch_async(dispatch_get_main_queue(), ^{ self.didFinishEventsForBackgroundURLSession(session); }); }}Copy the code

Official translation:

Function discussion:

  • In iOS, when a background transfer task is completed or a background transfer requires a certificate, and your app is suspended in the background, your app will automatically restart and run in the background. And the app UIApplicationDelegate will send a application: handleEventsForBackgroundURLSession: completionHandler: message. This message contains the corresponding session identifier in the background, and this message will cause your app to start. Your app should then store the Completion Handler first, then create a background configuration using the same identifier, Create a new session based on the background configuration. This newly created session is automatically re-associated with background tasks.
  • When your app for a URLSessionDidFinishEventsForBackgroundURLSession: news, this means that before this session has been team all message forwarding out, It is safe to call the previously accessed Completion Handler again at this point, or to call it as a result of an internal update.
NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { NSURLRequest *redirectRequest = request; // step1. See if there is a corresponding user block, if there is a user block, then forward it. Forward the request, the network redirects. If (self. TaskWillPerformHTTPRedirection) {/ / to use his or her own custom block to realize a redirect, return a new request. redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request); } if (completionHandler) { // step2. Rerequest the completionHandler(redirectRequest) with request; }}Copy the code
  • At first I thought the method was similarNSURLProtocolThis method is called only when the server is redirecting the request. To do this, I wrote a simple PHP test:
Copy the code

Verify that the proxy is invoked when our server redirects, and we can redefine the redirected request.

  • There are a few other things to note about this agent:

    This method is only called in the default session or Ephemeral session, whereas in the background session, the Session Task is automatically redirected.

The mode in question is the one we started Init with:

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

There are three modes:

For the need to use NSURLSessionConfiguration NSURLSession object is initialized, and NSURLSessionConfiguration has three kind of factory method: + defaultSessionConfiguration returns a standard configuration, the configuration and NSURLConnection network stack actually (networking stack) is the same, Have the same shared NSHTTPCookieStorage, shared NSURLCache, and shared NSURLCredentialStorage. + ephemeralSessionConfiguration returns a default configuration, this configuration will not to cache, cookies and certificates for persistent storage. This is ideal for features like secret browsing. + backgroundSessionConfiguration: (nsstrings *) the identifier is unique in that it creates a background session. Background sessions are different from regular, normal sessions in that they can run upload and download tasks even if the application hangs, exits, or crashes. The identifier specified at initialization is used to provide context to any daemons that may resume background transfers outside the process.

Moving on:

// HTTPS authentication - (void)URLSession (NSURLSession *) SessionTask (NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.taskDidReceiveAuthenticationChallenge) { disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if  ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); }}Copy the code
  • In view of the space, I will not post the translation of the official document, but to summarize: before we also had an HTTPS authentication, the function is the same, the implementation of the content is exactly the same.
  • The only difference is that there is an extra parameter task, and then calling our custom Block will return this task as a parameter, so that we can customize the HTTPS authentication method for each task.

To continue!

This proxy method is called when a session task needs to send a new Request Body stream to the server. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler { NSInputStream *inputStream = nil; // There is a custom taskNeedNewBodyStream, with custom, Or with the original stream in its task if (self. TaskNeedNewBodyStream) {inputStream = self. TaskNeedNewBodyStream (session, task); } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { inputStream = [task.originalRequest.HTTPBodyStream copy]; } if (completionHandler) { completionHandler(inputStream); }}Copy the code
  • The proxy method is called in two cases:
    1. If a task is founded by uploadTaskWithStreamedRequest:, then provide the initial request body when the stream will call the agent method.
    2. This proxy is called when the client needs to re-send a request containing the Body Stream due to authentication challenges or other recoverable server errors.

There's nothing to say about implementation. Next:

/* // Periodically notifies the agent of the progress of sending data to the server. */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { // If totalUnitCount fails to get, the Content-Length in the HTTP header is used as totalUnitCount INT64_t totalUnitCount = totalBytesExpectedToSend; if(totalUnitCount == NSURLSessionTransferSizeUnknown) { NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"]; if(contentLength) { totalUnitCount = (int64_t) [contentLength longLongValue]; } } if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); }}Copy the code
  • That is, every time you send data to the server, it calls back to this method to tell you how much has been sent and how much is to be sent.
  • The proxy method simply calls our custom Block.
Unfinished summary:
  • In fact, I have written so much that I have not yet reached the really important point, but because I am close to the maximum length of Jane's book, I can only conclude here first.
  • If you see this, you're a very patient, very studious, very nice iOS developer. I give you a thumbs up. Then I believe you are not stingy finger move, give this text point like... By the way, pay attention to the author... After all this writing... It's hard... Ahem, did I accidentally speak my mind?
  • Finally, if this article is reproduced, please note the source ~ thank you!
To be continued...