美文网首页iOS Blog网络
AFNetWorking源码之AFURLRequestSeria

AFNetWorking源码之AFURLRequestSeria

作者: NS西北风 | 来源:发表于2017-04-25 12:29 被阅读537次

    1 概述

    AFURLRequestSerialization主要实现了根据不同情况和参数初始化NSURLRequest对象的功能。只有AFHTTPSessionManager有requestSerialization,默认是AFHTTPRequestSerializer对象。尤其是我们使用MultipartForm请求的时候,可以使用它帮我们完成繁杂的请求头拼接过程,这个是最值得推荐的。

    在阅读源码之前,一定要对multipart/form-data非常熟悉,不然会有很多地方看不懂。具体可以看AFNetWorking源码之AFHTTPSessionManager关于它的那部分。

    2 AFURLRequestSerialization的api分析

    AFURLRequestSerialization包含了四个部分:

    • 全局方法:AFPercentEscapedStringFromStringAFQueryStringFromParameters
    • 协议AFURLRequestSerialization提供了一个序列化parameters参数的方法。我们可以把参数转换为查询字符串、HTTP请求体、设置恰当的请求头等。
    • AFHTTPRequestSerializer继承自AFURLRequestSerialization协议。提供了查询字符串/URL格式的参数序列化、默认请求头处理。同时以提供HTTP状态码和返回数据的验证等工作。
      _ AFMultipartFormData协议。主要用于添加multipart/form-data请求的Content-Disposition: file; filename=#{generated filename}; name=#{name}"Content-Type: #{generated mimeType}的请求体域。
    • 类型AFJSONRequestSerializerAFPropertyListRequestSerializer。主要针对JSON和Plist类型的序列化优化。

    AFPercentEscapedStringFromString返回一个字符串的百分号编码格式的字符串。因为url只有普通英文字符和数字,特殊字符$-_.+!*'()还有保留字符。所以很多字符都需要编码,非ASCII编码的字符串先转换为ASCII编码,然后再转换为百分号编码。

    /**
    AFPercentEscapedStringFromString方法的作用就是把一个普通字符串转换为百分号编码的字符串
     http://blog.csdn.net/qq_32010299/article/details/51790407
     @param string 一个字符串
     @return 百分号编码的字符串
     */
    NSString * AFPercentEscapedStringFromString(NSString *string) {
        //可能需要做百分号编码处理的字符串
        static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; 
        static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
        //不需要做百分号编码的字符串集合
        NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
        //获取目前系统中最终需要做百分号编码转换的字符集合
        [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
    
        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);
            //移除字符串中的一些非法字符。比如👴🏻👮🏽
            range = [string rangeOfComposedCharacterSequencesForRange:range];
            NSString *substring = [string substringWithRange:range];
            //指定范围内的字符做百分号编码
            NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
            [escaped appendString:encoded];
            index += range.length;
        }
        //返回处理以后的字符串
        return escaped;
    }
    

    私有类AFQueryStringPair的主要功能就是把一个key和vaue的键值对转换为百分号编码格式的键值对并且用=链接起来

    @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;
    }
    /**
     把key、value键值对转换为百分号编码,并且链接起来
     @return 转换后的字符串
     */
    - (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])];
        }
    }
    @end
    

    方法AFQueryStringPairsFromDictionaryAFQueryStringPairsFromKeyAndValue分别把一个字典或者key、value键值对转换为url的query参数。

    /**
     把一个字典转换为百分号编码的query参数
     @param parameters 要转换的字典
     @return query参数
     */
    NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
        NSMutableArray *mutablePairs = [NSMutableArray array];
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            //调用`AFQueryStringPair`序列化
            [mutablePairs addObject:[pair URLEncodedStringValue]];
        }
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
        return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
    }
    /**
     分别把一个字典、数组、集合转换为一个AFQueryStringPair对象的的数组。
     @param key key
     @param value value
     @return AFQueryStringPair类型数组
     */
    NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
        NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
        //使用`description`排序
        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) {
                    //如果是字典,就取出每一对key、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) {
                //如果是数组,则取出元素,添加一个额外的key处理
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
            }
        } else if ([value isKindOfClass:[NSSet class]]) {
            NSSet *set = value;
            for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
                //如果是集合,就是用默认key和集合元素处理
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
            }
        } else {
            //添加处理后的key和value
            [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
        }
        //返回`AFQueryStringPair`对象数组
        return mutableQueryStringComponents;
    }
    

    AFHTTPRequestSerializerObservedKeyPaths全局方法指定了request请求序列化要观察的属性列表、是一个数组,里面有对蜂窝数据、缓存策略、cookie、管道、网络状态、超时这几个元素。

    static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
        static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
        });
    
        return _AFHTTPRequestSerializerObservedKeyPaths;
    }
    
    

    2.1 AFHTTPRequestSerializer的解析

    AFHTTPRequestSerializer主要实现了大部分request拼接转化功能。比如通用请求头的添加如userAgent、request属性的KVO观察、手动指定请求头序列化的Block、负责具体的request对象的初始化等。

    1 AFHTTPRequestSerializer的属性和初始化

    //属性列表
    @interface AFHTTPRequestSerializer ()
    //某个request需要观察的属性集合
    @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
    //存储request的请求头域
    @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
    //用于修改或者设置请求体域的dispatch_queue_t。
    @property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue;
    @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
    //手动指定parameters参数序列化的Block
    @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
    @end
    //初始化方法
    - (instancetype)init {
        self = [super init];
        if (!self) {
            return nil;
        }
        //指定序列化编码格式
        self.stringEncoding = NSUTF8StringEncoding;
        //请求头保存在一个字典中,方便后面构建request的时候拼装。
        self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
        //初始化一个操作request的header域的dispatch_queue_t
        self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
    
        NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
        /*
         *枚举系统的language列表。然后设置`Accept-Language`请求头域。优先级逐级降低,最多五个。
         */
        [[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;
        userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
        if (userAgent) {
            /*
             *如果userAgent里面包含非ASCII码的字符,比如中文,则需要转换。这里是转换为对应的拉丁字母。
             AFNetWorking3.X源码阅读/1.0 (iPhone; iOS 10.2; Scale/2.00)
             AFNetWorking3.X yuan ma yue du/1.0 (iPhone; iOS 10.2; Scale/2.00)
             */
            if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
                NSMutableString *mutableUserAgent = [userAgent mutableCopy];
                //转换为拉丁字母
                if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                    userAgent = mutableUserAgent;
                }
            }
            [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
        }
    
        // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
        //需要把parameters转换为query参数的方法集合。
        self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
    
        self.mutableObservedChangedKeyPaths = [NSMutableSet set];
        /*
         添加对蜂窝数据、缓存策略、cookie、管道、网络状态、超时这几个属性的观察。
         */
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
        return self;
    }
    

    2 AFHTTPRequestSerializer的各种setter方法

    首先通过automaticallyNotifiesObserversForKey方法来阻止一些属性的KVO机制的触发,然后我们通过重写蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。

    /**
     如果kvo的触发机制是默认出发。则返回true,否则返回false。在这里,只要是`AFHTTPRequestSerializerObservedKeyPaths`里面的属性,我们都取消自动出发kvo机制,使用手动触发。
    
     @param key kvo的key
     @return bool值
     */
    + (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) {
            //如果属性值为null,则表示么有这个属性,移除对其的观察
            if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
                [self.mutableObservedChangedKeyPaths removeObject:keyPath];
            } else {
                //添加到要观察的属性的集合
                [self.mutableObservedChangedKeyPaths addObject:keyPath];
            }
        }
    }
    

    通过重写属性的setter方法来手动触发kvo

    #pragma mark - 手动触发蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。
    - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
        [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
        _allowsCellularAccess = allowsCellularAccess;
        [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    }
    
    - (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
        [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
        _cachePolicy = cachePolicy;
        [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
    }
    
    - (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
        [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
        _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
        [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    }
    
    - (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
        [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
        _HTTPShouldUsePipelining = HTTPShouldUsePipelining;
        [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
    }
    
    - (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
        [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
        _networkServiceType = networkServiceType;
        [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
    }
    
    - (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
        [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
        _timeoutInterval = timeoutInterval;
        [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
    }
    

    3 AFHTTPRequestSerializer的各种请求头域处理方法

    /**
     返回请求头域key和vaue
    
     @return 字典
     */
    - (NSDictionary *)HTTPRequestHeaders {
        NSDictionary __block *value;
        dispatch_sync(self.requestHeaderModificationQueue, ^{
            value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
        });
        return value;
    }
    
    /**
     设置一个请求头域
    
     @param value vaue
     @param field 域名
     */
    - (void)setValue:(NSString *)value
    forHTTPHeaderField:(NSString *)field
    {
        dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
            [self.mutableHTTPRequestHeaders setValue:value forKey:field];
        });
    }
    /**
     返回指定请求头域的值
    
     @param field 域名
     @return 值
     */
    - (NSString *)valueForHTTPHeaderField:(NSString *)field {
        NSString __block *value;
        dispatch_sync(self.requestHeaderModificationQueue, ^{
            value = [self.mutableHTTPRequestHeaders valueForKey:field];
        });
        return value;
    }
    
    /**
     设置Basic Authorization的用户名和密码。记住需要是base64编码格式的。
     @param username 用户
     @param password 密码
     */
    - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                           password:(NSString *)password
    {
        NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
        NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
        [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
    }
    /**
     移除Basic Authorization的请求头
     */
    - (void)clearAuthorizationHeader {
        dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
            [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
        });
    }
    

    4 AFHTTPRequestSerializer的各种创建NSMutableURLRequest的方法

    通过下面这三种方法处理不同类型的request对象的初始化和参数序列化。

    /**
     根据给定的url、方法名、参数构建一个request。
    
     @param method 方法名
     @param URLString url地址
     @param parameters 参数,根据不同的请求方法构建出不同的模式
     @param error 构建出错
     @return 返回一个非multipartForm请求
     */
    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(method);
        NSParameterAssert(URLString);
        NSURL *url = [NSURL URLWithString:URLString];
        NSParameterAssert(url);
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
        mutableRequest.HTTPMethod = method;
        /*
         *mutableObservedChangedKeyPaths集合里面的属性都通过`setValue: forKey`手动设置一下。估计目的是触发这几个属性的kvo。
         */
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
        /*
         根据parameters和HTTPRequestHeaders构建一个request
         */
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
        return mutableRequest;
    }
    
    /**
     构建一个multipartForm的request。并且通过`AFMultipartFormData`类型的formData来构建请求体
    
     @param method 方法名,一般都是POST
     @param URLString 请求地址
     @param parameters 请求头参数
     @param block 用于构建请求体的Block
     @param error 构建请求体出错
     @return 返回一个构建好的request
     */
    - (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];
    }
    
    /**
     通过一个Multipart-Form的request创建一个request。新request的httpBody是`fileURL`指定的文件。
     并且是通过`HTTPBodyStream`这个属性添加,`HTTPBodyStream`属性的数据会自动添加为httpBody。
    
     @param request 原request
     @param fileURL 文件的url
     @param handler 错误处理
     @return 处理完成的request
     */
    - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                                 writingStreamContentsToFile:(NSURL *)fileURL
                                           completionHandler:(void (^)(NSError *error))handler
    {
        NSParameterAssert(request.HTTPBodyStream);
        NSParameterAssert([fileURL isFileURL]);
        //获取`HTTPBodyStream`属性
        NSInputStream *inputStream = request.HTTPBodyStream;
        //获取文件的数据流
        NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
        __block NSError *error = nil;
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //把读和写的操作加入当前线程的runloop
            [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            //打开读和写数据流
            [inputStream open];
            [outputStream open];
            //循环做读和写操作
            while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
                uint8_t buffer[1024];
    
                NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
                if (inputStream.streamError || bytesRead < 0) {
                    error = inputStream.streamError;
                    break;
                }
    
                NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
                if (outputStream.streamError || bytesWritten < 0) {
                    error = outputStream.streamError;
                    break;
                }
    
                if (bytesRead == 0 && bytesWritten == 0) {
                    break;
                }
            }
            //读和写完成。关闭读和写数据流
            [outputStream close];
            [inputStream close];
            //如果有handler,调用handler这个Block
            if (handler) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler(error);
                });
            }
        });
        //获取一个新的request,新的request的httpBody已经通过`HTTPBodyStream`转换成功
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
        mutableRequest.HTTPBodyStream = nil;
        //返回一个request对象
        return mutableRequest;
    }
    

    3 AFStreamingMultipartFormData私有类的解析

    首先,我们看几个全局方法。下面几个方法用于拼接multipart/form-data的分隔符和文件的MIMEType

    /*
     生成multipartForm的request的boundary
     */
    static NSString * AFCreateMultipartFormBoundary() {
        return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
    }
    //回车换行符
    static NSString * const kAFMultipartFormCRLF = @"\r\n";
    //生成一个request的请求体中的参数的开始符号,第一个
    static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
        return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
    }
    //生成一个request的请求体中的参数的开始符号,菲第一个。
    static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
        return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
    }
    //生成一个request的请求体中的参数的结束符号
    static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
        return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
    }
    /*
    根据文件的扩展名获取文件的`MIMEType`
     */
    static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
        NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
        NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
        if (!contentType) {
            return @"application/octet-stream";
        } else {
            return contentType;
        }
    }
    

    AFStreamingMultipartFormData负责multipart/form-data的Body的具体构建。比如boundary的指定、请求体数据的拼接等。

    - (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                        stringEncoding:(NSStringEncoding)encoding
    {
        self = [super init];
        if (!self) {
            return nil;
        }
        //需要添加httpbody的request
        self.request = urlRequest;
        //字符编码
        self.stringEncoding = encoding;
        //指定boundary
        self.boundary = AFCreateMultipartFormBoundary();
        //这个属性用于存储httpbody数据
        self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
        return self;
    }
    /*
     根据文件的url添加一个`multipart/form-data`请求的请求体域
     */
    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                            error:(NSError * __autoreleasing *)error
    {
        NSParameterAssert(fileURL);
        NSParameterAssert(name);
        //文件扩展名
        NSString *fileName = [fileURL lastPathComponent];
        //获取文件的mimetype的类型
        NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
    
        return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
    }
    
    /**
     根据指定类型的fileurl,把数据添加进入bodyStream中。以提供给后面构建request的body。
    
     @param fileURL 文件的url
     @param name 参数名称
     @param fileName 文件名称
     @param mimeType 文件类型
     @param error 错误
     @return 是否成功
     */
    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                         mimeType:(NSString *)mimeType
                            error:(NSError * __autoreleasing *)error
    {
        NSParameterAssert(fileURL);
        NSParameterAssert(name);
        NSParameterAssert(fileName);
        NSParameterAssert(mimeType);
        /*
         各种错误情况判断
         */
        if (![fileURL isFileURL]) {
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)};
            if (error) {
                *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
            }
            return NO;
        } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
            NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)};
            if (error) {
                *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
            }
            return NO;
        }
        //获取指定路径文件的属性
        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
        if (!fileAttributes) {
            return NO;
        }
        //添加`Content-Disposition`和`Content-Type`这两个请求体域
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
        //把一个完整的请求体域封装进一个`AFHTTPBodyPart`对象中。
        AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
        bodyPart.stringEncoding = self.stringEncoding;
        bodyPart.headers = mutableHeaders;
        bodyPart.boundary = self.boundary;
        bodyPart.body = fileURL;
        bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
        [self.bodyStream appendHTTPBodyPart:bodyPart];
    
        return YES;
    }
    /**
     根据指定类型的数据流,把数据添加进入bodyStream中。以提供给后面构建request的body。
     
     @param inputStream 输入的数据流
     @param name 参数名称
     @param fileName 文件名称
     @param mimeType 文件类型
     */
    - (void)appendPartWithInputStream:(NSInputStream *)inputStream
                                 name:(NSString *)name
                             fileName:(NSString *)fileName
                               length:(int64_t)length
                             mimeType:(NSString *)mimeType
    {
        NSParameterAssert(name);
        NSParameterAssert(fileName);
        NSParameterAssert(mimeType);
        //添加`Content-Disposition`和`Content-Type`这两个请求体域
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
        //把一个完整的请求体域封装进一个`AFHTTPBodyPart`对象中
        AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
        bodyPart.stringEncoding = self.stringEncoding;
        bodyPart.headers = mutableHeaders;
        bodyPart.boundary = self.boundary;
        bodyPart.body = inputStream;
        bodyPart.bodyContentLength = (unsigned long long)length;
        [self.bodyStream appendHTTPBodyPart:bodyPart];
    }
    
    /**
     根据指定的data添加到请求体域中
    
     @param data 数据
     @param name 名称
     @param fileName 文件名称
     @param mimeType mimeType
     */
    - (void)appendPartWithFileData:(NSData *)data
                              name:(NSString *)name
                          fileName:(NSString *)fileName
                          mimeType:(NSString *)mimeType
    {
        NSParameterAssert(name);
        NSParameterAssert(fileName);
        NSParameterAssert(mimeType);
    
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
        
        [self appendPartWithHeaders:mutableHeaders body:data];
    }
    
    /**
     根据指定的key和value拼接到`Content-Disposition`属性中
    
     @param data 参数值
     @param name 参数名
     */
    - (void)appendPartWithFormData:(NSData *)data
                              name:(NSString *)name
    {
        NSParameterAssert(name);
    
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
        //把处理好的数据加入对应的request的请求体中`Content-Disposition`部分
        [self appendPartWithHeaders:mutableHeaders body:data];
    }
    
    /**
     给一个multipartForm的`Content-Disposition`添加boundary
    
     @param headers 请求头域
     @param body 值
     */
    - (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];
    }
    
    - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                      delay:(NSTimeInterval)delay
    {
        self.bodyStream.numberOfBytesInPacket = numberOfBytes;
        self.bodyStream.delay = delay;
    }
    
    /**
     根据一个request对应的`AFStreamingMultipartFormData`对象获取封装好的request对象
    
     @return multipart/form的request对象
     */
    - (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;
    }
    

    4 AFJSONRequestSerializerAFPropertyListRequestSerializer

    这两个类继承自AFHTTPRequestSerializer。他们的基本实现都是继承自父类。但是也根据自身不同情况,做了处理。

    对于AFJSONRequestSerializer。需要把Content-Type指定为"application/json。同时HTTPBody
    需要使用JSON序列化:

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
        /*
         对于`GET`,`HEAD`,`DELETE`等方法中。直接使用父类的处理方式
         */
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            return [super requestBySerializingRequest:request withParameters:parameters error:error];
        }
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
        //把`HTTPRequestHeaders`中的值添加进入请求头中。
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
        if (parameters) {
            //设置请求头的`Content-Type`类型
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
            }
    
            if (![NSJSONSerialization isValidJSONObject:parameters]) {
                if (error) {
                    NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
                    *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
                }
                return nil;
            }
            //把parameters转换为JSON序列化的data
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
            if (!jsonData) {
                return nil;
            }
            //JSON序列化的数据设置为httpbody
            [mutableRequest setHTTPBody:jsonData];
        }
        return mutableRequest;
    }
    

    对于AFPropertyListRequestSerializer也是同样的道理:

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
        /*
         对于`GET`,`HEAD`,`DELETE`等方法中。直接使用父类的处理方式
         */
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            return [super requestBySerializingRequest:request withParameters:parameters error:error];
        }
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
        //把`HTTPRequestHeaders`中的值添加进入请求头中。
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
        if (parameters) {
            //设置请求头的`Content-Type`类型
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
            }
            //把parameters转换为Plist序列化的data
            NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
            if (!plistData) {
                return nil;
            }
            //Plist序列化的数据设置为httpbody
            [mutableRequest setHTTPBody:plistData];
        }
        return mutableRequest;
    }
    

    5 总结

    这个类主要实现了对于不同情况的请求的request对象的封装。尤其是对于multipart/form-data类型的request的封装,简化了我们自己封装过程的痛苦。如果我们要使用multipart/form-data类型的请求。强烈推荐使用AFHTTPSessionManager对象的AFHTTPRequestSerialization来处理参数的序列化过程。下面就是使用AFHTTPRequestSerailization序列化和自己拼装的不同:

    - (IBAction)updatePic:(id)sender {
        //请求头参数
        NSDictionary *dic = @{
                              @"businessType":@"CC_USER_CENTER",
                              @"fileType":@"image",
                              @"file":@"img.jpeg"
                              };
        //请求体图片数据
        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) {
            //请求体里面的参数
            NSDictionary *bodyDic = @{
                                      @"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"",
                                      @"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];
    }
    - (IBAction)multipartformPost3:(id)sender {
        //参数
        NSDictionary *dic = @{
                              @"businessType":@"CC_USER_CENTER",
                              @"fileType":@"image",
                              @"file":@"img.jpeg"
                              };
        NSString *boundaryString = @"xxxxx";
        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]];
    
        
        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];
    
    }
    

    最后原文地址,demo地址

    相关文章

      网友评论

      • WXHWang_489a:请教个问题 采用post 方式上传json 类似这样的json {
        bid = (
        4d99def2949c9a90719cfd8c0ffce064,
        4d99def2949c9a90719cfd8c0ffce042
        );
        ventext = "cid=100";
        "deviceid" = f76a35d000bfe89920650d604d99def2;
        }

        但是此json 不做为参数上传
        作为请求体 采用 AFNetworking 的post 方式上传如何操作。不使用from方式
        NS西北风:@WXHWang_489a https://www.cnblogs.com/softidea/p/5745369.html
      • 丐帮头:写的不错

      本文标题:AFNetWorking源码之AFURLRequestSeria

      本文链接:https://www.haomeiwen.com/subject/gelvzttx.html