AFNetWorking源码之AFURLRequestSerialization

源码注释

简介

AFURLRequestSerialization可以理解成请求序列化的一个抽象,主要目地是生成一个NSURLRequest的请求对象,用于发送请求.AFHTTPRequestSerializer遵循该接口,实现了构建发起HTTP请求对象的功能,本篇文章主要介绍AFHTTPRequestSerializer.
它有以下几个优点:

  1. 配置相关NSMutableURLRequest的属性(比如,请求头,缓存策略,cookies,超时时间等)
  2. 对URL进行了PercentCode(百分比编码)或者称之为转义
  3. 根据不同的请求方式格式化请求参数
  4. multipart/from-data方式文件上传的简化处理

配置请求参数

是否允许使用设备的蜂窝移动网络来创建request,默认为允许:
 @property (nonatomic, assign) BOOL allowsCellularAccess;
缓存策略 默认使用NSURLRequestUseProtocolCachePolicy

NSURLRequestCachePolicy是一个枚举类型

  • NSURLRequestUseProtocolCachePolicy 这个是默认的缓存策略,缓存不存在,就请求服务器,缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端。
  • NSURLRequestReloadIgnoringLocalCacheData 这个策略是不管有没有本地缓存,都请求服务器。
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData 这个策略会忽略本地缓存和中间代理 直接访问源server
  • NSURLRequestReturnCacheDataElseLoad 这个策略指,有缓存就是用,不管其有效性,即Cache-Control字段 ,没有就访问源server
  • NSURLRequestReturnCacheDataDontLoad 这个策略只加载本地数据,不做其他操作,适用于没有网路的情况
  • NSURLRequestReloadRevalidatingCacheData 这个策略标示缓存数据必须得到服务器确认才能使用,未实现。
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
HTTPShouldHandleCookies布尔类型,默认YES

如果设置HTTPShouldHandleCookies为YES,就处理存储在NSHTTPCookieStore中的cookies
HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去

@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
HTTPShouldUsePipelining 布尔类型 默认NO

HTTPShouldUsePipelining表示客户端的下一个信息是否必须等到上一个请求回复才能发送。
如果为YES表示可以,NO表示必须等客户端收到先前的回复才能发送下个信息。
在HTTP连接中,一般都是一个请求对应一个连接,每次建立TCP连接是需要一定时间的。管线化,允许一次发送一组请求而不必等到响应。但由于目前并不是所有的服务器都支持这项功能,因此这个属性默认是不开启的。管线化使用同一TCP连接完成任务,因此能够大大节省提交请求的时间。但是响应要和请求的顺序 保持一致才行。使用场景:比如说首页要发送很多请求,可以考虑使用这种技术。但前提是建立连接成功后才可以使用。

@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
网络服务类型

枚举:默认NSURLNetworkServiceTypeDefault

@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
请求超时时间 60s
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
请求头
//请求头 默认包含 'Accept-Language':[NSLocale preferredLanguages]
//              'User-Agent':
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
//设置请求头
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
//获取请求头信息
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
凭证
//设置凭证
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password;
 //清除凭证
- (void)clearAuthorizationHeader;

对于请求头相关的设置,AFHTTPRequestSerializer创建了requestHeaderModificationQueue这个并行队列,用于设置和读取NSMutableURLRequest的属性.创建一个数组对象,承载所有需要配置的属性(蜂窝数据、缓存策略、cookie、管道、网络状态、超时),并手动实现KVO,对相关参数实时进行监听.

    //KVO 添加监听
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    //set方法
    - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
    [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    _allowsCellularAccess = allowsCellularAccess;
    [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
//自动监听,因为使用的手动KVO,判断自己手动监听的路径 返回NO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

将发生改变的keyPath装入到集合中,在构建Request对象的时候,取出对应的值进行赋值

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;
    
    //给NSMutableURLRequest自带的属性赋值 NSURLRequest/NSMutableURLRequest需要赋值的属性可以在AFHTTPRequestSerializerObservedKeyPaths()中找到
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
//        通过判断mutableObservedChangedKeyPaths(NSMutableSet)中是否有这个keyPath,来设定mutableRequest对应的keyPath值。
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

百分比编码|URL转义

RFC3986文档规定,URL中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符.所以对于一些特殊字符,比如空格,汉字,等需要进行转义,以避免造成接收URL的服务器解析错误.
每一个非ASCII字符都会被替换成”%XX”的形式,XX为两位16进制数,对应该字符在iso-8859-1字符集里面的编码。这个过程即URL转议,或者叫百分比编码

核心代码

NSString * AFPercentEscapedStringFromString(NSString *string) {
     //不包含 ? / 需要做百分比编码处理的字符串集合
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
     
     //进行字符串拼接,URLQueryAllowedCharacterSet是一个URL允许的字符集合,然后从这个集合中移除,即这个集合之外的都需要进行编码
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
     
     //以50为基准进行分割
    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;
     
    while (index < string.length) {
        
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as 👴🏻👮🏽
        //截取范围内的字符,避免表情(lenth = 2)的字符发生截取错误
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        //百分比编码
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
        [escaped appendString:encoded];

        index += range.length;
    }
    return escaped;
}

AFHTTPRequestSerializer遵循AFURLRequestSerialization协议,该协议定义了一个方法,用于通过传入的参数对URL进行序列化.

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

内部的实现机制,主要为设置请求头,对请求参数排序并进行百分比编码.不同的请求方式分别进行拼接.(HTTPHeader,HTTPBody),子类对象也可以重写该方法,实现自己的逻辑.比如AFPropertyListRequestSerializer,AFJSONRequestSerializer

我们一般使用AFNetworking都是使用的字典来承载需要传入的请求参数,内部通过迭代和递归(参数的值也有可能是集合类型的参数)来构建私有对象AFQueryStringPair,来分别对应key和value.并调用- (NSString *)URLEncodedStringValue实例方法来进行百分比编码.

//把类型为NSDictionary的参数处理为字符串(中间进行编码,排序)类型。
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    //遍历得到一个(key=value)类型的字符串,添加到数组中
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    //根据连接符&拼接成一个字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

//数组中包含AFQueryStringPair(参数字符串对)对象
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
    
    //排序,升序
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        //对字典的键进行排序以保证其连续性,当进行查询操作的时候,这对于反序列化可能模糊的序列(比如字典数组)是非常重要的.
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                //递归调用,比如value是字典,或者其他集合类型
                [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;
}

AFQueryStringPair的实例方法

//把左右的数据(key=value)使用AFPercentEscapedStringFromString函数进行百分比编码然后用=拼接起来。
- (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])];
    }
}

根据不同的请求方式格式化请求参数

HTTPMethodsEncodingParametersInURI这个集合表示需要将参数拼接到URL上的请求方式,默认包含GET HEAD DELETE,不在这个集合外的请求method将参数转化为二进制数据,放入到HTTPBody中.

    //GET HEAD DELETE 拼接在URL中
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            //空字符串
            query = @"";
        }
        //content-Type
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        //请求体
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }
    

Accept-Language 可接受的语言

    //Accept-Language 可接受的语言 q表示权重
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

User-Agent 发出请求的用户信息

    NSString *userAgent = nil;
#if TARGET_OS_IOS
    //iOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)",
                 [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey],//ExecutableKey或者BundleID
                 [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], //app版本
                 [[UIDevice currentDevice] model], //@"iPhone", @"iPod touch"
                 [[UIDevice currentDevice] systemVersion], //系统版本
                 [[UIScreen mainScreen] scale]];//屏幕scale
  [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];

Authorization HTTP授权的授权证书
示例: Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

//HTTP授权的授权证书    Authorization:Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    //Base64编码
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

- (void)clearAuthorizationHeader {
    dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
        [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
    });
}

multipart/from-data

什么是multipart/from-data

根据标准的http协议,我们的请求只能是OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE这几种。http协议是以ASCII码传输,建立在tcp, ip协议之上的应用层规范,http请求被分为了三个部分:状态行、请求头、请求体。
实际上,原始的http请求是不支持什么multipart或者www-form-urlencoded的,而所有的这些类型,实际上是对http请求体的一次封装。
如下:

  1. multipart/form-data的基础方法是post,也就是说是由post方法来组合实现的.
  2. multipart/form-data与post方法的不同之处:请求头,请求体。
  3. multipart/form-data的请求头必须包含一个特殊的头信息:Content-Type,且其值也必须规定为multipart/form-data,同时还需要规定一个内容分割符用于分割请求体中的多个post的内容,如文件内容和文本内容自然需要分割开来,否则服务器就无法正常解析和还原这个文件.
//${bound} 是一个占位符,代表我们规定的分割符,可以自己任意规定,但为了避免和正常文本重复了,尽量要使用复杂一点的内容。如AFNetWorking:[NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
Content-Type: multipart/form-data; boundary=${bound}

multipart/form-data的请求体也是一个字符串,不过和post的请求体不同的是它的构造方式,post是简单的name=value值连接,而multipart/form-data则是添加了分隔符等内容的构造体

Header = {"Content-type" : "multipart/form-data, boundary=AaB03x"}  
  
Data =  "--AaB03x\r\n" +  
        "content-disposition: form-data; name=\"field1\"\r\n" +  
        "\r\n" +   
        "Joe Blow\r\n" +  
        "--AaB03x\r\n" +  
        "content-disposition: form-data; name="pics"; filename=\"file1.txt\"\r\n" +  
        "Content-Type: text/plain\r\n" +  
        "\r\n" +  
        "" ... contents of file1.txt ...\r\n" +  
        "--AaB03x--\r\n" 
1

AFHTTPRequestSerialization构建multipart/form-data请求的方式:

 //请求头参数
    NSDictionary *dic = @{
                          @"businessType":@"CC_USER_CENTER",
                          @"fileType":@"image",
                          @"file":@"img.png"
                          };
    //请求体图片数据
    NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
    //创建request
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
    //post方法
    [request setHTTPMethod:@"POST"];
    
    AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
        //请求体里面的参数
        [formData appendPartWithFileData:imageData name:@"file" fileName:@"image.png" mimeType:@"image/png"];
//        NSDictionary *bodyDic = @{
//                                  @"Content-Disposition":@"form-data;name=\"file\";filename=\"img.png\"",
//                                  @"Content-Type":@"image/png",
//                                  };
//        [formData appendPartWithHeaders:bodyDic body:imageData];
    } progress:^(NSProgress * _Nonnull uploadProgress) {
        NSLog(@"上传进度");
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"上传成功:%@",responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"上传失败%@",error);
    }];
    [task resume];
    

AFStreamingMultipartFormData对象,用于构建NSMutableURLRequest的HTTPBodyStream.该对象遵循AFMultipartFormData协议,通过Block回调,调用相关代理方法,让我们可以进行添加数据流(通过NSData,文件路径,NSInputStream)和设置请求头(Content-Type,Content-Length)

- (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"]);
    /*
     先构建一个普通的request对象,然后在构建出multipartFrom的request
     * 在这一步将会把parameters加入请求头或者请求体。然后把`AFURLRequestSerialization`指定的headers加入request的请求头中。这个request就只差构建multipartFrom部分了
     */
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
    /*
     *初始化一个`AFStreamingMultipartFormData`对象。用于封装multipartFrom的body部分
     */
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    if (parameters) {
        /*
         把parameters拼接成`AFQueryStringPair`对象。然后根据取出的key和value处理。
         */
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            //把value处理为NSData类型
            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) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }
    if (block) {
        block(formData);
    }
    //body具体序列化操作
    return [formData requestByFinalizingMultipartFormData];
}
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }
    // Reset the initial and final boundaries to ensure correct Content-Length
    //重置boundary,从而确保`Content-Length`正确
    [self.bodyStream setInitialAndFinalBoundaries];
    //把拼接好的bodyStream添加进入request中
    [self.request setHTTPBodyStream:self.bodyStream];
    //给requst的请求头添加Content-Type属性指定为`multipart/form-data`类型的request。同时设置请求体的长度Content-Length。
    [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;
}

AFStreamingMultipartFormData有一个成员变量AFMultipartBodyStream,该类就好像一个管道,承载一个个的AFHTTPBodyPart对象,像读取数据流一样,添加请求体的一段段内容.

//对于NSInputStream的使用来说,我们要手动实现方法,当我们使用open打开流的时候,就会调用这个方法,我们需要在这个方法中处理我们的逻辑。
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;
    
    //初始边界
    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)];
    }
    
    //头
    if (_phase == AFHeaderPhase) {
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }
    
    //body
    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];
            }
        }
    }
    
    //结束边界
    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;
}

我们可以通过NSURLSesstion来构建一个multipart/from-data请求来类比一下:

//参数
    NSDictionary *dic = @{
                          @"businessType":@"CC_USER_CENTER",
                          @"fileType":@"image",
                          @"file":@"img.jpeg"
                          };
    //分隔符
    NSString *boundaryString = [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
    NSMutableString *str = [NSMutableString string];
    //请求参数
    [dic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        [str appendFormat:@"--%@\r\n",boundaryString];
        [str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key];
        [str appendFormat:@"%@\r\n",obj];
    }];
    
    NSMutableData *requestMutableData=[NSMutableData data];
    
    //分隔符
    [str appendFormat:@"--%@\r\n",boundaryString];
    //头
    [str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"];
    [str appendFormat:@"%@=\"%@\";",@"name",@"file"];
    [str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"];
    [str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"];
    //转换成为二进制数据 (主体)
    [requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
    NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]);
    //文件数据部分
    [requestMutableData appendData:imageData];
    //添加结尾boundary (结束边界)
    [requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]];

    NSLog(@"%@",[[NSString alloc]initWithData:requestMutableData encoding:NSUTF8StringEncoding]);
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]];
    //post方法
    [request setHTTPMethod:@"POST"];
    // 设置请求头格式为Content-Type:multipart/form-data; boundary=xxxxx
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"];
    request.HTTPBody = requestMutableData;
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",result);
    }];
    
    [task resume];
    ```

参考:
1.  [mutipart/form-data](http://blog.csdn.net/five3/article/details/7181521/) 
2.  [AFURLRequestSerialization](https://github.com/huang303513/iOSSourceCodeStudy)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容