AFNetWorking源碼之AFURLRequestSerialization

1 概述

AFURLRequestSerialization主要實現了根據不一樣狀況和參數初始化NSURLRequest對象的功能。只有AFHTTPSessionManager有requestSerialization,默認是AFHTTPRequestSerializer對象。尤爲是咱們使用MultipartForm請求的時候,可使用它幫咱們完成繁雜的請求頭拼接過程,這個是最值得推薦的。html

在閱讀源碼以前,必定要對multipart/form-data很是熟悉,否則會有不少地方看不懂。具體能夠看AFNetWorking源碼之AFHTTPSessionManager關於它的那部分。git

2 AFURLRequestSerialization的api分析

AFURLRequestSerialization包含了四個部分:github

  • 全局方法:AFPercentEscapedStringFromStringAFQueryStringFromParametersjson

  • 協議AFURLRequestSerialization提供了一個序列化parameters參數的方法。咱們能夠把參數轉換爲查詢字符串、HTTP請求體、設置恰當的請求頭等。api

  • 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編碼,而後再轉換爲百分號編碼。cookie

/**
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參數。session

/**
 把一個字典轉換爲百分號編碼的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地址

相關文章
相關標籤/搜索