zoukankan      html  css  js  c++  java
  • ios AFNetworking 3.0 原码阅读分析 (一)(AFURLRequestSerialization模块)

      本文主要内容是讲AFNetworking中的AFURLRequestSerialization。它主要的作用是在我们要发送一个网络请求的时候帮助我们创建NSMutableURLRequest并封装好所需要的参数到NSMutableURLRequest中。那它内部做了些什么,提供了什么功能,使得我们进行网络请求时变得如此方便、简单。好像我们什么都不用管就能建立一个正确的请求体NSURLRequest,我们只需要做的是选择一个满足自己需要的AFURLRequestSerialization即可。接下来就会一步步揭开它神秘的面纱。(ps:贴的源码由于一行太长,格式提难调的,可以打开XCode对着看)


      概览  

      首先看一下在AFURLRequestSerialization.h和AFURLRequestSerialization.m中所包含的类及它们间的关系,及各部分的功能。

    AFURLRequestSerialization协议:定义了一个如下方法,各子类会根据自己需要有自己的实现

    - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(nullable id)parameters
                                            error:(NSError * _Nullable __autoreleasing *)error

    下面写个伪代码说明AFHTTPRequestSerializer、AFJSONRequestSerializer、AFPropertyListRequestSerializer功能,里面囊括了AFURLRequestSerialization所有主要功能,用户进行网络请求时可根据自身需求选择这三个Serializer其中之一。这个很重要!!!

        if (请求方式为:get || head || delete)
        {  //场景(1) 后面讲细节的场景标记
         AFHTTPRequestSerializer、AFHTTPRequestSerializer、AFHTTPRequestSerializer
         其实都调到了AFHTTPRequestSerializer的
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error
    然后把parameters拼结成URL字符串参数加到URL后面作为请求的一部分。 }
    else if (multipart表单提交) {
         //场景(3) AFHTTPRequestSerializer、AFHTTPRequestSerializer、AFHTTPRequestSerializer就会
         都调用到AFHTTPRequestSerializer的
    - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable NSDictionary <NSString *, id> *)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block error:(NSError * _Nullable __autoreleasing *)error; 在这里面就会用到AFStreamingMultipartFormData,此时parameters及通过AFStreamingMultipartFormData添
         加进来的数据都会变一项项的AFHTTPBodyPart存起来,当URL系统需要上传数据时就通过AFHTTPBodyPart取数据。 }
    else //即((put||post||patch) && !multipart表单提交时) {
         //场景(2)
         AFHTTPRequestSerializer:把parameters拼接成字符串参数后(如何拼接细节后面写)
         序列化成NSData放到HTTPRequest的HTTPBody中。
    AFJSONRequestSerializer:把parameters以dataWithJSONObject方式序列化
         成NSData放到HTTPRequest的HTTPBody中。
    AFPropertyListRequestSerializer:把请求parameters以dataWithPropertyList
         方式序列化成NSData放到HTTPRequest的HTTPBody中。 }

     AFMultipartFormData协议:定义了一些接口方法,允许用户可以用不同的方式添加表单的内容,如:使用文件路径、直接用NSData、或使用inputStream等。

    AFStreamingMultipartFormData:遵循了AFMultipartFormData协议,把协议的方法都实现了。

    AFMultipartBodyStream:它起着一个重要桥梁作用,上传表单数据时系统会先调到它,然后它会依赖AFHTTPBodyPart读到数据,然后把数据返回给URL系统。

    AFHTTPBodyPart:每一个AFHTTPBodyPart就是代表一项表单数据,由它真正读取它内部的数据(不管是以什么形式存在的:文件路径,NSData,又或者NSInputStream)。

    正是因为这些类为我们做了这么多事情,所以当我们要进行网络请求,我们省去了对NSURLRequest请求体的构造,接下来就是实现的细节。


     实现细节

      在讲细节时,会按照上部分伪代码中标的序号写,然后在各部分流程中补充相应其它细节实现。所以接下来会出现较多源码。

    场景(1):下面是相关实现源码,可以看到在AFJSONRequestSerializer和AFPropertyListRequestSerializer对于AFURLRequestSerialization协议的实现都有相同的地方,如下:当请求方式为请求方式为:get || head || delete时调用了super并且return了,即调到了AFHTTPRequestSerializer的实现中。

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {//get || head || delete
            return [super requestBySerializingRequest:request withParameters:parameters error:error];
        }
    
        ... ...
        return mutableRequest;
    }

    那么我们接下来重点关注AFHTTPRequestSerializer的实现,关键的地方已经加上相关注释

    #pragma mark - AFURLRequestSerialization
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
        //设置http headers
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        NSString *query = nil;
        //参数不为空
        if (parameters) {
            //用户有自定义格式化参数的回调,自定义优先
            if (self.queryStringSerialization) {
                NSError *serializationError;
                query = self.queryStringSerialization(request, parameters, &serializationError);
    
                if (serializationError) {
                    if (error) {
                        *error = serializationError;
                    }
    
                    return nil;
                }
            } else {
            //用户没有自定义格式化参数则走这里
                switch (self.queryStringSerializationStyle) {
                    //目前只有AFHTTPRequestQueryStringDefaultStyle
                    case AFHTTPRequestQueryStringDefaultStyle:
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            if (query && query.length > 0) {
           //把格式化好的参数添加到URL尾部
    //This property contains the query string. Any percent-encoded characters are not unescaped. If the receiver does not conform to RFC 1808, this property contains nil. For example, in the URL http://www.example.com/index.php?key1=value1&key2=value2, the query string is key1=value1&key2=value2. 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 = @""; } if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } return mutableRequest; }

    可以看到,AFQueryStringFromParameters个函数我是加粗了的。先讲它的作用:把参数拼接成类似于key1=value1&key2=value2这样的一个字符串。源码如下:

    NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
        NSMutableArray *mutablePairs = [NSMutableArray array];
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            [mutablePairs addObject:[pair URLEncodedStringValue]];
        }
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    AFQueryStringPair类比较简单,整个类的定义及实现如下,就是包括了一个field和value,并提供一个方法URLEncodedStringValue用于返回拼接好后的key=value字符串。
    @interface AFQueryStringPair : NSObject
    @property (readwrite, nonatomic, strong) id field;
    @property (readwrite, nonatomic, strong) id value;
    
    - (instancetype)initWithField:(id)field value:(id)value;
    
    - (NSString *)URLEncodedStringValue;
    @end
    
    @implementation AFQueryStringPair
    
    - (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])];
        }
    }
    我们可以看到又多了个新的C函数AFPercentEscapedStringFromString,这个函数的作用是去除非法字符并且对特殊字符进行编码。这个可自行google学习URL规范相关知识,或点这里
    接下来我们看回
    AFQueryStringPairsFromDictionary,这里可能会有疑惑,传进来的不是已经是dictionary了吗?为什么还要转换成AFQueryStringPair呢?实现源码就不贴了,
    读者可以自行对源码看,我们想一下,传进来的dictionary如果是个多层及的dictionary,即value又是个dictionary或者是array那是又怎么样拼接参数呢。这正是AFQueryStringPairsFromDictionary要解决的问题。下面有具体例子,对着例子看源码会好理解一点。
    //AFQueryStringPairsFromDictionary它的作用:把多层级的参数扁平化成为一个数组,每个数组的元素是AFQueryStringPair
    //举些例子就能明白
    //@{key1:value1, key2:value2}  结果就是 [{key1:value1},{key2:vaule2}];
    //@{key:{key1:vaule}} 结果就是 [{key[key1]:vaule}]
    //@{key:[value]} 结果就是 [{key[]:value}]

     到此,对于场景(1)的细节都已经介绍完毕。

    场景(2):这一场景是最简单的,把数据接对应方法序列化后,填到HTTPBody中即可

    //对于AFHTTPRequestSerializer,会以application/x-www-form-urlencoded方式提交经过拼接后的query数据
    if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
           [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
     }
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        
    //对于AFJSONRequestSerializer,使用NSJSONSerialization直接序列化parameters
    if (parameters) {
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
            }
    
            [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
        }
    //对于AFPropertyListRequestSerializer,使用AFPropertyListRequestSerializer序列化parameters
    if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]]; }

    这种场景下,只是填好对应的Content-Type使用对应的方式序列化数据,把数据塞到httpbody中,然后提交给服务器即可。

      

     场景(3):这个场景是最为复杂的,在介绍这部份之前要先了解一下基本知识。对于multipart表单提交,请求会是如下这样的:

    请求header的Content-Type必需是这样的Content-Type: multipart/form-data; boundary=${bound}。${bound}是一个占位符,它可以是任意字符串。它是表单中每一part的分隔符。每一部分的内容都以--${bound}作为开始并以 结束,并且每一部分的内容都有它自己的header信息。最后会以--${bound}--作为所有内容都结束的标记。下面就是一个multipart表单提交的格式。

    --${bound}  
    Content-Disposition: form-data; name="name"  
    HTTP.pdf  
    --${bound}  
    Content-Disposition: form-data; name="name2"; filename="xxx" 
    data
    --${bound} Content-Disposition: form-data; name="uploaddata" uploaddata --${bound}--

    对应于AFNetworking,${bound}就是

    static NSString * AFCreateMultipartFormBoundary() {
        return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
    }

    这个boundary越复杂越好,避免出现重复。

    OK,有了以上基础知识,可以继续往下进行分析。与前两个场景不一样的是,对于multipart表单的请求,首先我们看它的对外接口:

    - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                                  URLString:(NSString *)URLString
                                                 parameters:(NSDictionary *)parameters
                                  constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                      error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(method);
        NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
        NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
        __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
        if (parameters) {
            for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
              ... ...

           if (data) { //把param数据加到AFMultipartBodyStream中 [formData appendPartWithFormData:data name:[pair.field description]]; } } } //通过block回调,给外部构造表单的每一个part,并且也会被添加到AFMultipartBodyStream中,具体看AFStreamingMultipartFormData的所有append前缀方法 if (block) { block(formData); } return [formData requestByFinalizingMultipartFormData]; }

    这里看加粗的requestByFinalizingMultipartFormData方法

    - (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
        if ([self.bodyStream isEmpty]) {
            return self.request;
        }
        
        // Reset the initial and final boundaries to ensure correct Content-Length
        //给第一part和最后一part数据加上标记,后面算法加boundary是会依赖这两个标记
        [self.bodyStream setInitialAndFinalBoundaries];
        [self.request setHTTPBodyStream:self.bodyStream];
        
        //设置Content-Type如刚基本知识所说的。
        [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
        //设置Content总长度
        [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
    
        return self.request;
    }

    继续这句 [self.request setHTTPBodyStream:self.bodyStream],这句是关键代码,当URL系统需要开始、停止、获取数据时,就会调用到被设置的bodyStream的以下几个方法:

    - (void)open;
    - (void)close;
    - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length; //其中这个是最键的,这就是一开始说的重要桥梁的接口,URL系统读取数据时就会调用到它。

     然后AFMultipartBodyStream中的read:maxLength:方法内部实现际又需要它的每个AFHTTPBodyPart的read:maxLength:方法读取正的数据。正如前面所说,每一个AFHTTPBodyPart都会以--${bound}作为开始并以 结束,AFNetworking作者就使用了一个状态机的模式去读取一个AFHTTPBodyPart的内容。总共会有四状态

    typedef enum {
        AFEncapsulationBoundaryPhase = 1,
        AFHeaderPhase                = 2,
        AFBodyPhase                  = 3,
        AFFinalBoundaryPhase         = 4,
    } AFHTTPBodyPartReadPhase;

    为了比较直观的理解它这个状态机是怎么样驱动的,接下来先看了流程图,再看源码就会好理解一点。

    下面就是它的源码,每次循环最多只能读取到AFHTTPBodyPart的一个状态内的内容,就是靠着AFMultipartBodyStream中的while循环去驱动AFHTTPBodyPart是状态机去读取内容。
    //AFMultipartBodyStream的read:maxLength:
    //
    由系统的回调调到AFMultipartBodyStream的read,取数据实际再从AFHTTPBodyPart去拿,AFMultipartBodyStream作用只中间桥梁 - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { if ([self streamStatus] == NSStreamStatusClosed) { return 0; } NSInteger totalNumberOfBytesRead = 0; while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { //判断读满了length没有 if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { //当前的part为空或不可读取数 if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { //已经读到最后一部份就退出了 break; } } else { //可读,计算还可以读取到buf中的最大长度maxLength,和buf的什么位置开始写进去即&buffer[totalNumberOfBytesRead] NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; //从当前可读的HTTPBodyPart读取数据 NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; //-1表示读取出错了 if (numberOfBytesRead == -1) { self.streamError = self.currentHTTPBodyPart.inputStream.streamError; break; } else { //读取成功后当前已经读的要加上实际读的长度numberOfBytesRead,然后再回到循环判断读满了length没有 totalNumberOfBytesRead += numberOfBytesRead; if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } } return totalNumberOfBytesRead; }

    //AFHTTPBodyPart的read:length:
    - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { NSInteger totalNumberOfBytesRead = 0; //如果是自己是第一个AFHTTPBodyPart读取状态是读取AFMultipartFormInitialBoundary,否则AFMultipartFormEncapsulationBoundary if (_phase == AFEncapsulationBoundaryPhase) { NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } //读header信息 if (_phase == AFHeaderPhase) { NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } //读真正内容 if (_phase == AFBodyPhase) { NSInteger numberOfBytesRead = 0; numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; if (numberOfBytesRead == -1) { return -1; } else { totalNumberOfBytesRead += numberOfBytesRead; if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { [self transitionToNextPhase]; } } } //读结束,如果自己是最后一个AFHTTPBodyPart则读取AFMultipartFormFinalBoundary,否则建立个空的NSData去读,相当于没读任何东西 if (_phase == AFFinalBoundaryPhase) { NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } return totalNumberOfBytesRead; }

     可以看到我有两行分加粗的,里面实现就不贴了,比较简单,说明一下transitionToNextPhase就是切换AFHTTPBodyPart到下一状态,除读取真正数据外其它状态要切换到下一状态都是在加粗的接口readData:intoBuffer:maxLength:中调用的。multipart表单的实现原理已经分析完毕。


       到这里整个AFURLRequestSerialization主要功能内容已经分析完毕。下一篇将会分析。。。

  • 相关阅读:
    ExtJS 使用点滴 四 XTemplate使用方法
    ExtJS 使用点滴 三 TreeGrid 单击事件侦听例子
    VS2008 引用App_Code下的类文件问题解决方法
    C# 文件操作类大全(转摘)
    SqlParameter数组
    ExtJS 使用点滴 二 如何使用XTemplate基于同行的其他列的值,改变当前列的显示样式
    ScriptManager.RegisterStartupScript方法
    ExtJS 使用点滴 一(XTemlpate)
    Jquery 远程调用 webService报错,500错误
    C# 调用数据库函数 转摘
  • 原文地址:https://www.cnblogs.com/chenxianming/p/5674652.html
Copyright © 2011-2022 走看看