AFNetworking-RequestSerializer

概况介绍:

这篇主要介绍AFNetworking中请求参数序列化的部分,具体代码在AFURLRequestSerialization中。AFURLRequestSerialization包含四部分:

  • AFHTTPRequestSerializaiton
  • AFJSONRequestSerializer
  • AFPropertyListRequestSerializer

AFHTTPRequestSerialization主要是设置http请求头,设置超时时间,BA认证,处理用户名密码登陆等等。主要功能分3大块:

  1. 处理所有的GET,HEAD,DELETE请求
  2. 处理content-type是application/x-www-form-urlencoded类型的POST请求
  3. 处理content-type是multipart/form-data类型的POST请求,请求的构建是通过AFStreamingMultipartFormData对象实现的。

AFJSONRequestSerializer继承自AFHTTPRequestSerialization类,使用NSJSONSerialization序列化json格式(application/json)的参数,将一个Dictionary对象转化成NSData,它只处理POST请求。

AFPropertyListRequestSerializer也继承了AFHTTPRequestSerialization类,使用NSPropertyListSerialization对象来序列化xml格式(application/x-plist)的参数,它也只处理POST请求。

综上所述,AFHTTPRequestSerialization是最重要也是最复杂的部分,源码也主要针对这部分做分析。

源码分析:

参数序列化和编码

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
 
                               withParameters:(id)parameters
 
                                        error:(NSError *__autoreleasing *)error
 
{
 
    NSParameterAssert(request);
 
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
//设置http请求头
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id 
        value, BOOL * __unused stop) {
 
        if (![request valueForHTTPHeaderField:field]) {
 
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
//序列化参数
    if (parameters) {
 
        NSString *query = nil;
//queryStringSerilization是一个block,主要是用来自定义参数序列化的逻辑,返回一个序列化完成的结果
        if (self.queryStringSerialization) {
 
            NSError *serializationError;
 
            query = self.queryStringSerialization(request, parameters, &
                serializationError);
 
            if (serializationError) {
 
                if (error) {
 
                    *error = serializationError;
 
                }
 
                return nil;
            }
        } else {
 
            switch (self.queryStringSerializationStyle) {
//使用AFNetworking默认的格式序列化,a=1&b=2这种
                case AFHTTPRequestQueryStringDefaultStyle:
//这里parameters是一个dictionary对象,stringEncoding是给httpBody设置data的时候字符
//串的编码格式
                    query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);
 
                    break;
 
            }
 
        }
//HTTPMethodsEncodingParametersInURI定义了GET,DELETE,HEAD3种方法
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request 
            HTTPMethod] uppercaseString]]) {
 
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? 
            @"&%@" : @"?%@", query]];
 
        } else {
//POST
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
 
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
 
            }
//setHTTPBody接受一个NSData的参数,将query转化成NSData
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    }
 
    return mutableRequest;
}
//用于AFURLRequestSerialization内部调用的方法
static NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
 
    NSMutableArray *mutablePairs = [NSMutableArray array];
//AFQueryStringPair是封装的键值对对象,主要是将dictionary中的键值对转化成query 
//string形式,包括一些特殊字符的编码
//AFQueryStringPairsFromDictionary将dictionary转化成AFQueryStringPair集合
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
 
        [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
 
    }
    return [mutablePairs componentsJoinedByString:@"&"];
 
}
- (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
//碰到nil或者NSNULL边界值的处理
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field
         description], stringEncoding);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field 
            description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value 
                description], stringEncoding)];
    }
}
//主要调用foundation函数CFURLCreateStringByAddingPercentEscapes,它主要是将querystri
//ng中的特殊字符(&,?)编码成“%+ASCII” 形式。根据文档,建议使用NSString 
//stringByAddingPercentEncodingWithAllowedCharacters:]方法,这个方法使用UTF-8 encoding。
static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(
    NSString *string, NSStringEncoding encoding) {
    return (__bridge_transfer  NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge 
        CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, 
        CFStringConvertNSStringEncodingToEncoding(encoding));
}

AFStreamingMutipartFormData

先看一个mutipart/form-data格式的请求

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
 
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
 
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
 
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
  1. 它的Content-Type包含两部分,第一部分是multipart/form-data,第二部分是boundary, boundary一般是随机生成的,保持唯一即可。上面还需要有content-length,图里面没有。
  2. 每个参数的部分都有content-disposition,并且以--boundary开头,最后一个参数以--boundary--结尾。

AFNetworking通过AFStreamingMutipartFormData处理multipart/form-data格式的POST请求,它通过NSInputStream来构建http请求的body。每个参数的信息封装到了AFHTTPBodyPart中,所有的参数信息封装在AFMulipartBodyStream中。AFHTTPBodyPart中有NSInputStream对象用来将每个参数的写入到body,而AFMutipartBodyStream继承子NSInputStream处理所有参数的写入到body。其结构示意如下:

AFNetworkingArch
//处理multilpart/form-data(POST)请求
- (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"]);
//这里parameters传的是nil,因为当前格式(mutilpart/form-data)
//需要由AFStreamingMulitpartFormData去构建参数。
    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)) {
//对于dictionary的参数,直接将它的值转成NSData处理,
            NSData *data = nil;
 
            if ([pair.value isKindOfClass:[NSData class]]) {
 
                data = pair.value;
 
            } else if ([pair.value isEqual:[NSNull null]]) {
 
                data = [NSData data];
 
            } else {
 
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
 
            }
 
            if (data) {
//AFStreamingMultipartFormData对象的appendPartWithFormData将键值对转化成AFHTTPBodyP
//art并且放到AFMultipartBodyStream集合中。
                [formData appendPartWithFormData:data name:[pair.field description]];
 
            }
 
        }
 
    }
 
    if (block) {
//当前block用来处理非dictionary的情况,比如参数可能是一个NSURL,或者直接就是一个NSInputStream。
        block(formData);
 
    }
//AFStreamingMultipartFormData的requestByFinalizingMultipartFormData主要是设置cont
//ent-Type和content-Length以及boundary
    return [formData requestByFinalizingMultipartFormData];
 
}
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
 
    if ([self.bodyStream isEmpty]) {
 
        return self.request;
 
    }
 
    // 设置boundary
 
    [self.bodyStream setInitialAndFinalBoundaries];
 
//将AFStreamingMultipartFormData的AFMultipartBodyStream设置到http body stream上
    [self.request setHTTPBodyStream:self.bodyStream];
 
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; 
    boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
 
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream
     contentLength]] forHTTPHeaderField:@"Content-Length"];
 
    return self.request;
 
}
- (void)setInitialAndFinalBoundaries {
 
    if ([self.HTTPBodyParts count] > 0) {
 
        for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
 
            bodyPart.hasInitialBoundary = NO;
 
            bodyPart.hasFinalBoundary = NO;
 
        }
//设置boundary的头
        [[self.HTTPBodyParts objectAtIndex:0] setHasInitialBoundary:YES];
//设置boundary的尾
        [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
 
    }
 
}
//主要是设置每个参数部分的content-disposition
- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name
 
{
    NSParameterAssert(name);
 
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
 
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@
    \"", name] forKey:@"Content-Disposition"];
 
    [self appendPartWithHeaders:mutableHeaders body:data];
 
}
//构建AFHTTPBodyPart对象,这种情况下,AFHTTPBody对象的body都是nsdata类型
- (void)appendPartWithHeaders:(NSDictionary *)headers
                         body:(NSData *)body
 
{
    NSParameterAssert(body);
 
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
 
    bodyPart.stringEncoding = self.stringEncoding;
 
    bodyPart.headers = headers;
 
    bodyPart.boundary = self.boundary;
 
    bodyPart.bodyContentLength = [body length];
 
    bodyPart.body = body;
 
    [self.bodyStream appendHTTPBodyPart:bodyPart];
 
}

除了NSData,还可以传入NSInputStream,NSURL去构建AFHTTPBodyPart对象,过程和NSData类似,设置Content-disposition和Content-Type,再通过data去构建AFHTTPBodyPart。

AFMultipartBodyStream

- (NSInteger)read:(uint8_t *)buffer
 
        maxLength:(NSUInteger)length
{
//AFStreamingMutipartFormData将AFMultipartBodyStream设置到NSUrlRequest的httpbodys
//tream之后,foundation会自动调用read:maxLength:方法,改方法实现中遍历之前构建的所有AFHTTP
//BodyPart对象,分别调用它们的read:maxLength:方法来获取数据。


    if ([self streamStatus] == NSStreamStatusClosed) {
 
        return 0;
 
    }
 
    NSInteger totalNumberOfBytesRead = 0;
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wgnu"
 
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.
        numberOfBytesInPacket)) {
 
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart 
            hasBytesAvailable]) {
 
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator 
                nextObject])) {
 
                break;
 
            }
 
        } else {
 
            NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
 
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&
            buffer[totalNumberOfBytesRead] maxLength:maxLength];
 
            if (numberOfBytesRead == -1) {
 
                self.streamError = self.currentHTTPBodyPart.inputStream.
                streamError;
 
                break;
 
            } else {
 
                totalNumberOfBytesRead += numberOfBytesRead;
 
                if (self.delay > 0.0f) {
 
                    [NSThread sleepForTimeInterval:self.delay];
 
                }
 
            }
 
        }
 
    }
    return totalNumberOfBytesRead;
 
}

AFHTTPBodyPart

//根据body类型生成对应的inputStream
- (NSInputStream *)inputStream {
 
    if (!_inputStream) {
 
        if ([self.body isKindOfClass:[NSData class]]) {
 
            _inputStream = [NSInputStream inputStreamWithData:self.body];
 
        } else if ([self.body isKindOfClass:[NSURL class]]) {
 
            _inputStream = [NSInputStream inputStreamWithURL:self.body];
 
        } else if ([self.body isKindOfClass:[NSInputStream class]]) {
 
            _inputStream = self.body;
 
        } else {
            _inputStream = [NSInputStream inputStreamWithData:[NSData data]];
        }
    }
 
    return _inputStream;
}
- (NSInteger)read:(uint8_t *)buffer
 
        maxLength:(NSUInteger)length
 
{
 
    NSInteger totalNumberOfBytesRead = 0;
    if (_phase == AFEncapsulationBoundaryPhase) {
//boundary部分,转成NSData
        NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? 
            AFMultipartFormInitialBoundary(self.boundary) : 
            AFMultipartFormEncapsulationBoundary(self.boundary)) 
            dataUsingEncoding:self.stringEncoding];
 
        totalNumberOfBytesRead += [self readData:encapsulationBoundaryData 
        intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (
            NSUInteger)totalNumberOfBytesRead)];
 
    }
    if (_phase == AFHeaderPhase) {
//content-disposition和content-length,转成NSData
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.
        stringEncoding];
 
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer
        [totalNumberOfBytesRead] maxLength:(length - (NSUInteger)
        totalNumberOfBytesRead)];
 
    }
 
    if (_phase == AFBodyPhase) {
        NSInteger numberOfBytesRead = 0;
//每个AFHTTPBodyPart的body部分,也就是实际传输的数据部分,都通过在inputStream方法里根据其实
//际数据类型转化成了NSInputStream类型对象,所以这里只需要调用foundation自带的read:maxLength方法就行了。
        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];
            }
        }
    }
 
    if (_phase == AFFinalBoundaryPhase) {
//如果当前AFHTTPBodyPart是最后一个参数,那么会比其他参数多一个--boundary--的部分,转成NSData
        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;
 
} 
//将data读入buffer中
- (NSInteger)readData:(NSData *)data
 
           intoBuffer:(uint8_t *)buffer
 
            maxLength:(NSUInteger)length
 
{
 
#pragma clang diagnostic push
 
#pragma clang diagnostic ignored "-Wgnu"
/*没有明白为什么读data的时候range为什么要从_phaseReadOffset开始,不应该是从0开始吗,因为每次
都是一个全新的data*/
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length]
     - ((NSUInteger)_phaseReadOffset), length));
//将data中range范围之内的数据复制到buffer里
    [data getBytes:buffer range:range];
 
#pragma clang diagnostic pop
 
    _phaseReadOffset += range.length;
 
    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
 
        [self transitionToNextPhase];
 
    }
    return (NSInteger)range.length;
 
}
//序列化AFHTTPBodyPart的headers部分
- (NSString *)stringForHeaders {
    NSMutableString *headerString = [NSMutableString string];
 
    for (NSString *field in [self.headers allKeys]) {
 
        [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", 
        field, [self.headers valueForKey:field], kAFMultipartFormCRLF]];
 
    }
    [headerString appendString:kAFMultipartFormCRLF];
    return [NSString stringWithString:headerString];
 
} 
//计算每个AFHTTPBodyPart的内容长度,包括传输参数,请求头和boundary信息,在AFMultiStream里会
//将所有的AFHTTPBodyPart的content-length加起来,做为整个POST请求体的content-length。
- (unsigned long long)contentLength {
    unsigned long long length = 0;
//boundary
    NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? 
        AFMultipartFormInitialBoundary(self.boundary) : 
    AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self
    .stringEncoding];
    length += [encapsulationBoundaryData length];
//headers
    NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.
    stringEncoding];
    length += [headersData length];
//data
    length += _bodyContentLength;
//close boudary
    NSData *closingBoundaryData = ([self hasFinalBoundary] ? [
        AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.
    stringEncoding] : [NSData data]);
    length += [closingBoundaryData length];
    return length;
 
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容