AFNetworking到底长啥样(下)

发布时间:2020-07-27 09:34:35 作者:zlayne
来源:网络 阅读:416

在AFNetworking到底长啥样(上)中简单介绍了AFN涉及的主要类及其结构,接下来以一个简单的POST请求探寻其内部是如何实现的。

一、环境搭建

  1. 服务器配置

    本例中直接使用iMac自带的Apache,并为其开启PHP支持。在服务器目录下编写index.php文件如下:

    <?php
    echo @"This is Layne's Response";
    ?>
  2. 编写测试App

    创建一个测试App,在主界面上增加一个按钮,在按钮的点击函数中发起网络请求,如下:

    - (AFHTTPSessionManager *)manager{//lazy
       if(!_manager){
           _manager = [AFHTTPSessionManager manager];
       }
       return _manager;
    }
    
    - (void)click{
       [self.manager POST:@"http://www.layne.com" 
               parameters:@{@"name":@"layne",@"age":@30} 
                  headers:@{@"TestName":@"myTest"} 
                 progress:nil 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSLog(@"success:%@",responseObject);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    NSLog(@"fail:%@",error);
       }];
    }

二、一个POST请求的前世今生

启动测试App,点击按钮。接下里让我们看看AFN是如何优雅的管理网络请求的。

1.初始化

至此必要的初始化已完成。

2.发起请求

(1) POST

进入到POST函数中:

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                                headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters headers:headers uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];

    [dataTask resume];

    return dataTask;
}

即最终生成一个dataTask,然后resume启动,并将dataTask返回。

(2) 生成dataTask

生成的dataTask是通过如下函数实现的,本例中传入的参数值也已标明:

-  dataTaskWithHTTPMethod:  //@"POST"
                URLString:  //@"http://www.layne.com"
               parameters:  //@{@"name":@"layne",@"age":@30}
                  headers:  //@{@"TestName":@"myTest"} 
           uploadProgress:  //nil
         downloadProgress:  //nil
                  success:  //successBlock{NSLog(@"success:%@",responseObject);}
                  failure:  //failureBlock{NSLog(@"fail:%@",error);}

其内部执行的步骤如下:

Step1:调用requestSerializer的如下方法创建mutableRequest:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method   //@"POST"
                                 URLString:(NSString *)URLString//@"http://www.layne.com"
                                parameters:(id)parameters//@{@"name":@"layne",@"age":@30}
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;//设置为POST

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }//根据用户是否设置了allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval来设置mutableRequest对应字段值。本例中由于未进行任何设置,因此上述逻辑不会有效果。

    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];//①

    return mutableRequest;
}

①是调用AFURLRequestSerialization协议方法对mutableRequest进行进一步处理,包括:

至此,生成的mutableRequest结构如下:

mutableRequest{
URL:          "http://www.layne.com"
Method:       "POST"
Header:       "Accept-Language" = "en;q=1";
              "User-Agent" = "TestApp/1.0 (iPhone; iOS 13.1; Scale/3.00)";
              "Content-Type" = "application/x-www-form-urlencoded";
HttpBody      <6167653d 3330266e 616d653d 6c61796e 65>  //name=layne&age=30(utf-8)
}

Step2:合并传入的header

for (NSString *headerField in headers.keyEnumerator) {
        [request addValue:headers[headerField] forHTTPHeaderField:headerField];
}

执行之后mutableRequest结构如下:

mutableRequest{
URL:          "http://www.layne.com"
Method:       "POST"
Header:       "Accept-Language" = "en;q=1";
              "User-Agent" = "TestApp/1.0 (iPhone; iOS 13.1; Scale/3.00)";
              "Content-Type" = "application/x-www-form-urlencoded";
              "TestName":"myTest";
HttpBody      <6167653d 3330266e 616d653d 6c61796e 65>  //name=layne&age=30(utf-8)
}

Step3:判断Step1中生成mutableRequest的过程是否出错,若出错,则调用failureBlock并返回。

if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }
        return nil;
 }

Step4:根据mutableRequest生成dataTask。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request //request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock//nil
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock//nil
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler /*block{
                                  if (error) {
                                      if (failure) {
                                          failure(dataTask, error);
                                      }
                                  } else {
                                      if (success) {
                                          success(dataTask, responseObject);
                                      }
                                  }        
                                             }*/
{

    __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;//③
}

说明:

①中不直接生成dataTask,是因为在iOS8以下的系统上,若是在并行队列上创建dataTask会导致completionHandler调用出错。因此为了解决这个问题,针对iOS8以下系统,AFN使用自己维护的一个串行队列来创建dataTask。具体问题描述如下:

Due to this bug: http://openradar.appspot.com/radar?id=5871104061079552 in NSURLSessionTask, creating tasks on a concurrent queue can cause incorrect completionHandlers to get called.

When a duplicate taskIdentifier is returned by the task, the previous completionHandler gets cleared out and replaced with the new one. If the data for the first request comes back before the second request's data, the first response is then called against the second completionHandler.

I'm not sure what AFNetworking should do here — it could enforce creating tasks on a serial queue or it could just advise people to do so. We could also add a test to assert that the taskIdentifier is not a duplicate?

②的作用是为dataTask生成对应的delegate,以调用回调方法。

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask //dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock//nil
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock//nil
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler/*block{
                                  if (error) {
                                      if (failure) {
                                          failure(dataTask, error);
                                      }
                                  } else {
                                      if (success) {
                                          success(dataTask, responseObject);
                                      }
                                  }        
                                             }*/
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];//a
    delegate.manager = self;//b
    delegate.completionHandler = completionHandler;//c

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;//d
    [self setDelegate:delegate forTask:dataTask];//e

    delegate.uploadProgressBlock = uploadProgressBlock;//nil
    delegate.downloadProgressBlock = downloadProgressBlock;//nil
}

a. 为dataTask生成AFURLSessionManagerTaskDelegate实例。delegate内部维护着:

一个mutableData用来保存收到的response data。

一个uploadProgress和downloadProgress用来标明上传/下载进度。它们的cancel、suspend和resume操作与dataTask进行了绑定,即通过对它们进行cancel、suspend和resume操作就可以操作dataTask的cancel、suspend和resume。此外,还采用KVO监听uploadProgress和downloadProgress的进度变化,从而调用用户自定义的block:uploadProgressBlockdownloadProgressBlock

b. delegate弱引用当前的manager。

c.delegate保存完成回调。

③返回最终的dataTask,并启动。

(3) 处理Response

AFN是基于NSURLSession的,涉及以下几个协议:

NSURLSessionDelegate

 - URLSession:didBecomeInvalidWithError:
 - URLSession:didReceiveChallenge:completionHandler:
 - URLSessionDidFinishEventsForBackgroundURLSession:

NSURLSessionTaskDelegate

 - URLSession:willPerformHTTPRedirection:newRequest:completionHandler:
 - URLSession:task:didReceiveChallenge:completionHandler:
 - URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
 - URLSession:task:needNewBodyStream:
 - URLSession:task:didCompleteWithError:
 - URLSession:task:didFinishCollectingMetrics:

NSURLSessionDataDelegate

 - URLSession:dataTask:didReceiveResponse:completionHandler:
 - URLSession:dataTask:didBecomeDownloadTask:
 - URLSession:dataTask:didReceiveData:
 - URLSession:dataTask:willCacheResponse:completionHandler:

NSURLSessionDownloadDelegate

 - URLSession:downloadTask:didFinishDownloadingToURL:
 - URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:
 - URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

回调调用顺序:

① URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

由于使用的是POST方式,因此该回调是首先调用的。逻辑:找到task对应的delegate=>调用delegate的URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:来更新delegate维护的uploadProgress。若用户自定义了taskDidSendBodyData block,则调用。

② URLSession:task:didFinishCollectingMetrics:

该方法调用时机不定,用来收集整个请求的信息。逻辑:找到task对应的delegate=>调用delegate的URLSession:task:didFinishCollectingMetrics:来为delegate.sessionTaskMetrics赋值。若用户自定义了taskDidFinishCollectingMetrics block,则调用。

③ URLSession:dataTask:didReceiveData:
收到response data时执行。逻辑:找到task对应的delegate=>调用delegate的URLSession:task:didCompleteWithError:来更新delegate内部维护的downloadProgress并使用mutableData保存response data。若用户自定义了dataTaskDidReceiveData block,则调用。

④ URLSession:task:didCompleteWithError:

这是最后执行的回调。逻辑:找到task对应的delegate=>调用delegate的URLSession:task:didCompleteWithError:,并删除delegate和task的对应关系。若用户自定义了taskDidComplete block,则调用。

数据的处理主要在delegate的URLSession:task:didCompleteWithError:中,主要做了以下几件工作:

经过上面的处理,最终要么error要么responseObject会返回到POST的回调中去。整个网络请求就完成了。

三、AFNetworking中的干货

上面我们跟着一个简单的POST请求了解了AFN的整个工作流程,但还有一些细节我觉得还是值得我们学习的。

  1. 如果response不是标准格式的JSON数据或者我们需要对原始data进行加密解密操作该如何做?

    答:将responseSerializer设置为AFHTTPResponseSerializer的实例,这样response data 会以原始data的形式返回给上层。AFHTTPResponseSerializer仅针对code和contenttype进行校验,而默认的AFJSONResponseSerializer除了校验code和contenttype之外还对JSON格式进行校验,并直接解析成NSDictionary。当然,如果想对response data想要做最全面的自定义处理,最直接的方式当然是自定义AFHTTPResponseSerializer的子类,并重写协议方法

    - (id)responseObjectForResponse:(NSHTTPURLResponse *)response
                              data:(NSData *)data
                             error:(NSError *__autoreleasing *)error
  2. 如果返回的JSON数据中包含NSNull数据该如何处理?

    答:将responseSerializer(AFJSONResponseSerializer实例)的removesKeysWithNullValues属性设置为YES。这样一来在JSON转换为NSDictionary之后会将NSDictionary中的NSNull值去除。

  3. 如果我想自己更改请求参数的格式(即不用默认的name=layne&age=30这种)该如何设置?

    答:调用requestSerializer的-setQueryStringSerializationWithBlock:设置自定义的格式。在requestSerializer处理参数的时候会先去判断queryStringSerialization block是否为空,若不为空,则使用该block处理参数。若为空,则使用默认的格式(如name=layne&age=30)生成参数串。

  4. 除了使用GET/POST方法的回调,还可以通过什么方式获得网络请求结果?

    答:还可以使用notification。AFN中有一个名为AFNetworkingTaskDidCompleteNotification的通知,可以通过监听该通知获取网络请求结果。AFURLSessionManagerTaskDelegate的URLSession:task:didCompleteWithError:中在调用completeHandler之后会发送通知:

    dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                   if (self.completionHandler) {
                       self.completionHandler(task.response, responseObject, serializationError);//回调
                   }
    
                   dispatch_async(dispatch_get_main_queue(), ^{
                       [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];//将task和userInfo一起广播出去
                   });
               });
  5. 如何更改回调所在的线程?

    答:指定manager的completeQueue。这样回调就会在其他线程中执行。

    _manager.completionQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
  6. AFURLSessionManager如何获取NSURLSession维护的task?

    答:使用getTasksWithCompletionHandler:API并使用信号量保证线程安全。

    - (NSArray *)tasksForKeyPath:(NSString *)keyPath { //tasks、uploadTask、downloadTasks
       __block NSArray *tasks = nil;
       dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
           if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
               tasks = dataTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
               tasks = uploadTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
               tasks = downloadTasks;
           } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
               tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
           }
    
           dispatch_semaphore_signal(semaphore);
       }];
    
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
       return tasks;
    }
  7. 若我想取消一个刚发起的网络请求该如何做?

    答:AFHTTPSessionManager的POST/GET方法返回的是task对象,直接[task cancel]即可。如:

    NSURLSessionDataTask *task = [self.manager POST:@"http://www.layne.com" 
               parameters:@{@"name":@"layne",@"age":@30} 
                  headers:@{@"TestName":@"myTest"} 
                 progress:nil 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSLog(@"success:%@",responseObject);
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    NSLog(@"fail:%@",error);
       }];
    [task cancel];//直接cancel
  8. 若我想取消所有task该如何做?

    答:使用如下方法。建议resetSession传入YES将session重置,否则下次网络请求会crash,并提示:“Attempted to create a task in a session that has been invalidated

    - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks resetSession:(BOOL)resetSession {
       if (cancelPendingTasks) {
           [self.session invalidateAndCancel];
       } else {
           [self.session finishTasksAndInvalidate];
       }
       if (resetSession) {
           self.session = nil;
       }
    }
    例如:
    [self.manager invalidateSessionCancelingTasks:YES resetSession:YES];

至此AFNetworking是如何工作的我们就知道了。看了源码之后不得不感叹作者真是神人,不仅优雅的给出了NSURLSession的使用范例,而且还包含了业务层面的巧妙设计。嗯,阅读源码使我快乐^_^。

推荐阅读:
  1. AFNetworking到底长啥样(上)
  2. 如何找到MySQL长事务

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

afnetworking源码 post afn

上一篇:修改spyder背景色的方法

下一篇:高速缓冲存储器有什么作用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》