
目錄
- 前言
- 流程圖
- 核心代碼
- 請(qǐng)求頭
- 請(qǐng)求體
- 1、通過(guò)Parameters(參數(shù)字典)構(gòu)建的請(qǐng)求體
- 1、用戶自定義block轉(zhuǎn)譯
- 2、AFN默認(rèn)的轉(zhuǎn)義方式:
- 將字典轉(zhuǎn)化成字符串并且轉(zhuǎn)譯
- 根據(jù)不同情況將參數(shù)字符串拼接到URL后、或者放入請(qǐng)求體
- 2、通過(guò)文件構(gòu)建的請(qǐng)求體
- AFMultipartFormData協(xié)議
- AFHTTPBodyPart
-
AFStreamingMultipartFormData
- 核心方法(將文件分段拼接)
- 1、通過(guò)Parameters(參數(shù)字典)構(gòu)建的請(qǐng)求體
- AFHTTPRequestSerializer
- AFHTTPRequestSerializer.h
- AFHTTPRequestSerializer.m
- NSInputStream和NSOutputSteam的使用
- APIDemo
- 參考資料
前言
AFNetworking源碼第四篇
主要看了看AFURLRequestSerialization的內(nèi)容
負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求NSMutableURLRequest對(duì)象的初始化
以及請(qǐng)求頭、請(qǐng)求體、參數(shù)、上傳文件的自動(dòng)化配置
幾千行代碼、很長(zhǎng)。但是讀下來(lái)會(huì)受益匪淺
AFN概述:《iOS源碼補(bǔ)完計(jì)劃--AFNetworking 3.1.0源碼研讀》
流程圖
里面的文件實(shí)在是太多了、還是先弄個(gè)流程圖好一些?

解釋圖中幾個(gè)相關(guān)的類:
- AFHTTPRequestSerializer
負(fù)責(zé)請(qǐng)求的生產(chǎn) - AFQueryStringPair
內(nèi)部有兩個(gè)屬性。分別代表字典的key和value。
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
在將請(qǐng)求參數(shù)拼接進(jìn)URL或請(qǐng)求體的時(shí)候會(huì)用到。
- AFHTTPBodyPart
請(qǐng)求體單個(gè)片段(也就是單個(gè)name) - AFStreamingMultipartFormData
用來(lái)整合請(qǐng)求體信息、并且整合進(jìn)request - AFMultipartFormData
負(fù)責(zé)上傳文件拼接的協(xié)議、AFStreamingMultipartFormData對(duì)象就遵從這個(gè)協(xié)議。內(nèi)部將會(huì)先轉(zhuǎn)化成AFHTTPBodyPart然后追加給AFStreamingMultipartFormData。
包括追加文件、二進(jìn)制文件、數(shù)據(jù)流等等。
核心代碼
還是先寫(xiě)一個(gè)普通的請(qǐng)求代碼:
AFHTTPRequestSerializer * requestSerializer = [AFHTTPRequestSerializer serializer];
[requestSerializer setValue:@"請(qǐng)求頭value1" forHTTPHeaderField:@"請(qǐng)求頭key1"];
[requestSerializer setValue:@"請(qǐng)求頭value2" forHTTPHeaderField:@"請(qǐng)求頭key2"];
NSMutableURLRequest * req = [requestSerializer requestWithMethod:@"POST" URLString:@"http://127.0.0.1:3000/" parameters:@{@"key":@"value"} error:nil];
[[[AFHTTPSessionManager manager] dataTaskWithRequest:req completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
}] resume] ;
AFN內(nèi)部的操作很直觀
1、生成一個(gè)_MRequest
2、如果你設(shè)置了一些通用屬性、則覆蓋一下。
3、設(shè)置請(qǐng)求頭請(qǐng)求體
//通過(guò)請(qǐng)求方式、URL、參數(shù)字典生成請(qǐng)求
- (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];
//設(shè)置請(qǐng)求方式
mutableRequest.HTTPMethod = method;
//如果某個(gè)關(guān)鍵屬性被自主設(shè)置過(guò)、則用新的。不然直接用模板生成的`NSMutableURLRequest`即可
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//對(duì)req進(jìn)一步設(shè)置(拼接URL、請(qǐng)求體、請(qǐng)求頭)
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
//監(jiān)聽(tīng)集合。蜂窩網(wǎng)絡(luò)、緩存策略、cookie、管線鏈接、網(wǎng)絡(luò)服務(wù)類型、超時(shí)連接
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;
}
第三步mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];尤為重要、它完善了整個(gè)request的必要信息。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//設(shè)置請(qǐng)求頭
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//根據(jù)參數(shù)parameters設(shè)置查詢字段
NSString *query = nil;
if (parameters) {
//看看參數(shù)是否需要用戶自定義轉(zhuǎn)譯
if (self.queryStringSerialization) {
NSError *serializationError;
//調(diào)用用戶block、獲得參數(shù)轉(zhuǎn)譯的字符串
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
//使用AFN的默認(rèn)轉(zhuǎn)譯方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
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 {
//否則、則放入請(qǐng)求體
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
至此一個(gè)正常的請(qǐng)求就結(jié)束了。
不過(guò)、既然是讀源碼、肯定還需要把每個(gè)代碼都好好看看。
請(qǐng)求頭
請(qǐng)求頭可以通過(guò)外界設(shè)置、保存在字典里。在生成request的時(shí)候批量添加設(shè)置進(jìn)去。
/*
Get方法
*/
- (NSDictionary *)HTTPRequestHeaders {
//將不可變字典轉(zhuǎn)換成可變字典
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
/*
設(shè)置請(qǐng)求頭
*/
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
//對(duì)請(qǐng)求頭字典進(jìn)行追加
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
//根據(jù)不同的key提取出value
return [self.mutableHTTPRequestHeaders valueForKey:field];
}
//通過(guò)賬號(hào)密碼設(shè)置授權(quán)請(qǐng)求頭
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
//轉(zhuǎn)化成data
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
//base64編碼加密
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
//加入請(qǐng)求頭字典
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
//清除授權(quán)用請(qǐng)求頭
- (void)clearAuthorizationHeader {
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}
針對(duì)AFN而不是multipart 協(xié)議來(lái)看、可以講的沒(méi)有多少。
請(qǐng)求體
-
1、通過(guò)
Parameters(參數(shù)字典)構(gòu)建的請(qǐng)求體
既然提到Parameters、那么就像上面代碼里寫(xiě)的一樣、所有的Parameters字典、都需要被轉(zhuǎn)義成字符串。
AFN為我們提供了兩種轉(zhuǎn)換的方式。
- 1、用戶自定義block轉(zhuǎn)譯
沒(méi)啥說(shuō)的、參數(shù)都給你。自己轉(zhuǎn)譯完還給AFN字符串就行
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
self.queryStringSerialization = block;
}
- 2、AFN默認(rèn)的轉(zhuǎn)義方式:
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
雖然是個(gè)switch、但是queryStringSerializationStyle默認(rèn)目前只被提供了一種:
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
AFHTTPRequestQueryStringDefaultStyle = 0,
};
但是寫(xiě)成枚舉、是為了將來(lái)更好的擴(kuò)展和維護(hù)。像這種有可能擴(kuò)展的地方、是我們值得借鑒的。
-
將字典轉(zhuǎn)化成字符串并且轉(zhuǎn)譯
//將字典參數(shù)轉(zhuǎn)化成字符串
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
//將AFQueryStringPair對(duì)象轉(zhuǎn)化成key=value的格式并且傳遞給新的數(shù)組
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//開(kāi)頭添加"&"
return [mutablePairs componentsJoinedByString:@"&"];
}
//將字典轉(zhuǎn)化成數(shù)組{key1[key2]value}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
//把key && value 轉(zhuǎn)成數(shù)組
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]]) {
//參數(shù)value為字典
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
/*
[dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]
對(duì)所有的字典里的nestedKey進(jìn)行排序
*/
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
//獲取nestedKey里的value
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
if (key) {
//如果指明了key、則為二級(jí)字典。用key[nestedKey]進(jìn)行遍歷
//比如@{aaa:@{bbb:ccc}};就會(huì)被變成aaa[bbb]作為key
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%@]", key, nestedKey], nestedValue)];
}else {
//如果沒(méi)有傳入nestedKey、則直接為一級(jí)字典。用subkey進(jìn)行遍歷
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(nestedKey, nestedValue)];
}
// [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 {
//如果是字符串、則將key、value組合
//如果是字典的話就會(huì)出現(xiàn)key = @"aaaa[bbbb]" value = @"cccc"這種情況、后面會(huì)再轉(zhuǎn)化
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
//返回最終的`AFQueryStringPair`對(duì)象數(shù)組
return mutableQueryStringComponents;
}
- 字典轉(zhuǎn)字符串
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
利用遞歸的方式對(duì)深層次的參數(shù)(value是NSDictionary/NSArray/NSSet)進(jìn)行拆分。 - AFQueryStringPair對(duì)象
@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
用來(lái)儲(chǔ)存每個(gè)field(key)對(duì)應(yīng)的value。
比如@{aaa:@{bbb:ccc}};就會(huì)被變成aaa[bbb]作為field
- 轉(zhuǎn)譯方法
將每個(gè)AFQueryStringPair轉(zhuǎn)譯成字符串[pair URLEncodedStringValue]
如果有value則使用key=value、否則直接轉(zhuǎn)譯key
- (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])];
}
}
RFC 3986 規(guī)范下需要被保留的字符
":", "#", "[", "]", "@", "?", "/"
"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
在RFC 3986 規(guī)范 Section 3.4 下 的查詢字段、除了"?","/"以外的特殊字符都應(yīng)該被轉(zhuǎn)義。所以有了以下這個(gè)方法:
/**
對(duì)字符串編碼
*/
NSString * AFPercentEscapedStringFromString(NSString *string) {
//":", "#", "[", "]", "@", "?", "/" 除去"?","/"
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
//"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
//將以上兩種設(shè)置為排除
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
/*
只是[NSCharacterSet URLQueryAllowedCharacterSet]的話以上字符默認(rèn)是不轉(zhuǎn)譯的
需要移出去。只保留"?"和"/"不轉(zhuǎn)譯
*/
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
//每次最多轉(zhuǎn)譯50個(gè)字符
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as ????????
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
-
根據(jù)不同情況將參數(shù)字符串拼接到URL后、或者放入請(qǐng)求體
//如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
//原url帶有參數(shù)、則拼接'&'。沒(méi)參數(shù)則拼接'?'
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
//否則、則放入請(qǐng)求體
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
是否拼接到URL取決于HTTPMethodsEncodingParametersInURI這個(gè)屬性。
用戶可以自定義追加或者刪除這個(gè)合集的內(nèi)容。
/**
需要拼接參數(shù)的請(qǐng)求方法、默認(rèn)包含`GET``HEAD``DELETE`
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
-
2、通過(guò)文件構(gòu)建的請(qǐng)求體
NSMutableURLRequest * request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"baidu" parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *fileURL = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"1.txt"]];
[formData appendPartWithFileURL:fileURL name:@"userfile[]" error:NULL];
NSURL *fileURL1 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"2.jpg"]];
[formData appendPartWithFileURL:fileURL1 name:@"userfile[]" fileName:@"aaa.jpg" mimeType:@"image/jpeg" error:NULL];
} error:nil];
- AFMultipartFormData協(xié)議
文件的方式主要通過(guò)協(xié)議AFMultipartFormData進(jìn)行、有很多添加方式。
@protocol AFMultipartFormData
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
body:(NSData *)body;
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end
在進(jìn)行必要的Content-Type生成后、都會(huì)流入以下方法:
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];
將AFHTTPBodyPart實(shí)例、追加進(jìn)self.bodyStream(AFStreamingMultipartFormData實(shí)例)
- AFHTTPBodyPart
單個(gè)請(qǐng)求體文件
@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (nonatomic, strong) NSDictionary *headers;//頭信息
@property (nonatomic, copy) NSString *boundary;//邊界
@property (nonatomic, strong) id body;//內(nèi)容
@property (nonatomic, assign) unsigned long long bodyContentLength;//內(nèi)容大小
@property (nonatomic, strong) NSInputStream *inputStream;//流
@property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始邊界
@property (nonatomic, assign) BOOL hasFinalBoundary;//是否有結(jié)束邊界
@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
@property (readonly, nonatomic, assign) unsigned long long contentLength;//長(zhǎng)度
//讀取數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length;
@end
-
AFStreamingMultipartFormData
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//請(qǐng)求request
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (readwrite, nonatomic, copy) NSString *boundary;//邊界
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body文件整合工具
@end
繼承自NSInputStream、因?yàn)樽詈笮枰獙⒄麄€(gè)實(shí)例接入給request.bodyStream。
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
//如果body為空、則直接返回request
return self.request;
}
// Reset the initial and final boundaries to ensure correct Content-Length
[self.bodyStream setInitialAndFinalBoundaries];
//將請(qǐng)求體接入request
[self.request setHTTPBodyStream:self.bodyStream];
//multipart協(xié)議請(qǐng)求頭
[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;
}
我們注意到有兩個(gè)請(qǐng)求頭的設(shè)置、是multipart協(xié)議獨(dú)有的。
其中、self.bodyStream是我們正在說(shuō)的AFStreamingMultipartFormData實(shí)例。
self.boundary是邊界分隔符。
在AFStreamingMultipartFormData初始化時(shí)一并初始完成。
@implementation AFStreamingMultipartFormData
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding
{
self = [super init];
if (!self) {
return nil;
}
self.request = urlRequest;
self.stringEncoding = encoding;
self.boundary = AFCreateMultipartFormBoundary();
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
//其中
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
初次之外、還有三個(gè)函數(shù)
//如果是開(kāi)頭分隔符的,那么只需在分隔符結(jié)尾加一個(gè)換行符
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
//如果是中間部分分隔符,那么需要分隔符前面和結(jié)尾都加換行符
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
//如果是末尾,還得使用--分隔符--作為請(qǐng)求體的結(jié)束標(biāo)志
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
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;
}
}
服務(wù)于第二個(gè)請(qǐng)求頭[self.bodyStream contentLength]
//計(jì)算body大小
- (unsigned long long)contentLength {
unsigned long long length = 0;
//初始邊界 是否有初始邊界?初始邊界:封裝邊界(比初始邊界多了個(gè)開(kāi)頭的\r\n)
NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
length += [encapsulationBoundaryData length];
//頭長(zhǎng)度
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
length += [headersData length];
//內(nèi)容長(zhǎng)度
length += _bodyContentLength;
//結(jié)束邊界 是否有結(jié)束邊界?結(jié)束邊界:0
NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
length += [closingBoundaryData length];
return length;
}
初始化、設(shè)置邊界、追加body文件、驗(yàn)證有效性
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = encoding;
self.HTTPBodyParts = [NSMutableArray array];
self.numberOfBytesInPacket = NSIntegerMax;
return self;
}
//設(shè)置初始邊界和結(jié)束邊界
- (void)setInitialAndFinalBoundaries {
if ([self.HTTPBodyParts count] > 0) {
for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
bodyPart.hasInitialBoundary = NO;
bodyPart.hasFinalBoundary = NO;
}
[[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
[[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
}
}
//追加body文件
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
[self.HTTPBodyParts addObject:bodyPart];
}
//是否為空
- (BOOL)isEmpty {
return [self.HTTPBodyParts count] == 0;
}
-
核心方法(將文件分段拼接)
之所以是核心方法、因?yàn)檫@是AFN的一個(gè)亮點(diǎn)。
平時(shí)在用NSURLRequest上傳文件時(shí),一般是兩種方法:
1、一個(gè)是設(shè)置body、但是如果文件稍大的話,將會(huì)撐爆內(nèi)存。
2、另外一種則是創(chuàng)建一個(gè)臨時(shí)文件、將數(shù)據(jù)拼接進(jìn)去、然后將文件路徑設(shè)置為bodyStream、這樣就可以分片的上傳了。
而AFN這種方式無(wú)疑更好、具體來(lái)看:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
//總大小
NSInteger totalNumberOfBytesRead = 0;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//遍歷讀取數(shù)據(jù)
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
//body不存在或者沒(méi)有可讀字節(jié)
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
//把下一個(gè)body文件賦值給當(dāng)前body
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
//存在可讀數(shù)據(jù)
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
//把當(dāng)前body讀取到buffer中。內(nèi)部會(huì)采用遞歸的方式將數(shù)據(jù)分段寫(xiě)入buffer
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
#pragma clang diagnostic pop
return totalNumberOfBytesRead;
}
AFHTTPRequestSerializer
其實(shí)到這已經(jīng)沒(méi)什么特別難得東西了、只剩下一些流程任務(wù)
-
AFHTTPRequestSerializer.h
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
/**
編碼方式。默認(rèn)`NSUTF8StringEncoding`
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
允許蜂窩網(wǎng)絡(luò)
*/
@property (nonatomic, assign) BOOL allowsCellularAccess;
/**
請(qǐng)求的緩存策略
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
//默認(rèn)策略。緩存存在則根據(jù)request中Cache-Control字段進(jìn)行不同操作、否則則請(qǐng)求
NSURLRequestUseProtocolCachePolicy = 0,
//每次都請(qǐng)求服務(wù)器
NSURLRequestReloadIgnoringLocalCacheData = 1,
//忽略本地緩存和中間代理。直接請(qǐng)求原服務(wù)器 -- 未實(shí)現(xiàn)
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
//同NSURLRequestReloadIgnoringLocalCacheData
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
//有緩存就是用、不管其有效性。即忽略Cache-Control字段、沒(méi)有就訪問(wèn)源server
NSURLRequestReturnCacheDataElseLoad = 2,
//只加載本地?cái)?shù)據(jù),不做其他操作,適用于沒(méi)有網(wǎng)路的情況
NSURLRequestReturnCacheDataDontLoad = 3,
//緩存數(shù)據(jù)必須得到服務(wù)器確認(rèn)才能使用--未實(shí)現(xiàn)
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
是否使用默認(rèn)的cookie處理、默認(rèn)YES
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
管線化傳輸、默認(rèn)NO
正常的HTTP請(qǐng)求都是一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)連接、每次TCP鏈接需要一定的時(shí)間。
管線化:允許一次發(fā)送一組請(qǐng)求而不必等到響應(yīng)。但并不是所有服務(wù)器都支持、需要和服務(wù)器協(xié)商。并且需要提前建立好鏈接才能使用
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
網(wǎng)絡(luò)服務(wù)類型
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
NSURLNetworkServiceTypeDefault = 0, // 普通網(wǎng)絡(luò)傳輸,默認(rèn)使用這個(gè)
NSURLNetworkServiceTypeVoIP = 1, // 網(wǎng)絡(luò)語(yǔ)音通信傳輸,只能在VoIP使用
NSURLNetworkServiceTypeVideo = 2, // 影像傳輸
NSURLNetworkServiceTypeBackground = 3, // 網(wǎng)絡(luò)后臺(tái)傳輸,優(yōu)先級(jí)不高時(shí)可使用。對(duì)用戶不需要的網(wǎng)絡(luò)操作可使用
NSURLNetworkServiceTypeVoice = 4 // 語(yǔ)音傳輸
};
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
超時(shí)時(shí)間
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
///---------------------------------------
/// @name Configuring HTTP Request Headers
///---------------------------------------
/**
請(qǐng)求頭
*/
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
/**
初始化一個(gè)默認(rèn)配置的req
*/
+ (instancetype)serializer;
/**
設(shè)置httpheader、如果value為nil、則移除field(key)
*/
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
/**
返回field(key)標(biāo)記的內(nèi)容
*/
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
設(shè)置請(qǐng)求頭中的`Authorization`字段
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
/**
清空請(qǐng)求頭
*/
- (void)clearAuthorizationHeader;
///-------------------------------------------------------
/// @name 請(qǐng)求參數(shù)配置
///-------------------------------------------------------
/**
需要拼接參數(shù)的請(qǐng)求方法、默認(rèn)包含`GET``HEAD``DELETE`
*/
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
查詢參數(shù)的轉(zhuǎn)義樣式.(目前只有一種)
*/
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
/**
你也可以自定義參數(shù)的轉(zhuǎn)義方式
*/
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
///-------------------------------
/// 核心方法
///-------------------------------
/**
如果請(qǐng)求方式為GET`, `HEAD`, or `DELETE時(shí)、參數(shù)會(huì)被拼接到URL中、否則當(dāng)做body處理
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/**
支持上傳數(shù)據(jù)
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
/**
這個(gè)方法可以把一個(gè)帶有bodystream請(qǐng)求體的req、轉(zhuǎn)化成不含bodystream的req。
其中的文件會(huì)被轉(zhuǎn)寫(xiě)到fileURL的路徑下。
你可以通過(guò)upload直接上傳
NSURLSessionTask中有一個(gè)bug,當(dāng)HTTP body的內(nèi)容是來(lái)自NSStream的時(shí)候,request無(wú)法發(fā)送Content-Length到服務(wù)器端,此問(wèn)題在Amazon S3的Web服務(wù)中尤為顯著。
*/
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
@end
-
AFHTTPRequestSerializer.m
@interface AFHTTPRequestSerializer ()
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;//記錄修改過(guò)的屬性
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;//請(qǐng)求頭字典
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;//參數(shù)轉(zhuǎn)義的樣式
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;//自定義參數(shù)轉(zhuǎn)義block
@end
@implementation AFHTTPRequestSerializer
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
/**
* 傳遞可接受的語(yǔ)言,q代表對(duì)語(yǔ)言的喜好程度,默認(rèn)是取出前5個(gè)的數(shù)據(jù),不足5個(gè),取實(shí)際的個(gè)數(shù)
*/
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;
}];
//放到請(qǐng)求頭里
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
//獲取用戶信息
NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_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], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; 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], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
if (userAgent) {
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;
}
}
//將用戶信息寫(xiě)入請(qǐng)求頭
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
//批量添加監(jiān)聽(tīng)
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
- (void)dealloc {
//移除監(jiān)聽(tīng)
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
}
}
}
#pragma mark -
// Workarounds for crashing behavior using Key-Value Observing with XCTest
// See https://github.com/AFNetworking/AFNetworking/issues/2523
- (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))];
}
#pragma mark -
/*
對(duì)請(qǐng)求頭進(jìn)行操作
*/
- (NSDictionary *)HTTPRequestHeaders {
//將不可變字典轉(zhuǎn)換成可變字典
return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
//對(duì)請(qǐng)求頭字典進(jìn)行追加
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
//根據(jù)不同的key提取出value
return [self.mutableHTTPRequestHeaders valueForKey:field];
}
//通過(guò)賬號(hào)密碼設(shè)置授權(quán)請(qǐng)求頭
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password
{
//轉(zhuǎn)化成data
NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
//base64編碼加密
NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
//加入請(qǐng)求頭字典
[self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}
//清除授權(quán)用請(qǐng)求頭
- (void)clearAuthorizationHeader {
[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}
#pragma mark -
//設(shè)置查詢參數(shù)的編碼方式
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
self.queryStringSerializationStyle = style;
self.queryStringSerialization = nil;
}
//設(shè)置查詢參數(shù)自定義編碼的block
- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
self.queryStringSerialization = block;
}
#pragma mark -
//通過(guò)請(qǐng)求方式、URL、參數(shù)字典生成請(qǐng)求
- (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];
//設(shè)置請(qǐng)求方式
mutableRequest.HTTPMethod = method;
//如果某個(gè)關(guān)鍵屬性被自主設(shè)置過(guò)、則用新的。不然直接用模板生成的`NSMutableURLRequest`即可
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//對(duì)req進(jìn)一步設(shè)置(拼接URL、請(qǐng)求體、請(qǐng)求頭)
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
/*
數(shù)據(jù)上傳專用處理
不允許GET&&HEAD方法
參數(shù)將會(huì)拼接到formdata中以表單的形式上傳
*/
- (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"]);
//生成req
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
// 創(chuàng)建一個(gè)`AFStreamingMultipartFormData`實(shí)例,用來(lái)處理數(shù)據(jù)。
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
//遍歷參數(shù)字典(轉(zhuǎn)化成的數(shù)組)
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
NSData *data = nil;
if ([pair.value isKindOfClass:[NSData class]]) {
data = pair.value;
} else if ([pair.value isEqual:[NSNull null]]) {
data = [NSData data];
} else {
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
//將每個(gè)參數(shù)拼接到`AFStreamingMultipartFormData`對(duì)象中
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
if (block) {
block(formData);
}
//將請(qǐng)求體數(shù)據(jù)接入請(qǐng)求、并且返回處理完成的req
return [formData requestByFinalizingMultipartFormData];
}
//將請(qǐng)求體中的文件、剝離出來(lái)
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler
{
//是否有HTTPBodyStream
NSParameterAssert(request.HTTPBodyStream);
//是否是個(gè)文件
NSParameterAssert([fileURL isFileURL]);
//獲取寫(xiě)入對(duì)象
NSInputStream *inputStream = request.HTTPBodyStream;
// 使用outputStream將HTTPBodyStream的內(nèi)容寫(xiě)入到路徑為fileURL的文件中
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;
//異步寫(xiě)入
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//開(kāi)啟讀寫(xiě)工具
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
//讀取數(shù)據(jù) `hasBytesAvailable`代表是否有可用字節(jié)
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
//將數(shù)據(jù)讀取到buffer、每次最大讀取1024
NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}
//將buffer寫(xiě)入
NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}
//如果讀取與寫(xiě)入都為0、代表轉(zhuǎn)寫(xiě)結(jié)束
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
[outputStream close];
[inputStream close];
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
});
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
return mutableRequest;
}
#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
//設(shè)置請(qǐng)求頭
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//根據(jù)參數(shù)parameters設(shè)置查詢字段
NSString *query = nil;
if (parameters) {
//看看參數(shù)是否需要用戶自定義轉(zhuǎn)譯
if (self.queryStringSerialization) {
NSError *serializationError;
//調(diào)用用戶block、獲得參數(shù)轉(zhuǎn)譯的字符串
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
//使用AFN的默認(rèn)轉(zhuǎn)譯方式
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//如果請(qǐng)求方式是需要將查詢參數(shù)拼接到URL后面的(默認(rèn)包含`GET``HEAD``DELETE`)、則拼接
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
//原url帶有參數(shù)、則拼接'&'。沒(méi)參數(shù)則拼接'?'
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
//否則、則放入請(qǐng)求體
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
#pragma mark - NSKeyValueObserving
//可以決定是否發(fā)送KVO通知
+ (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]]) {
//如果沒(méi)有新值、則清空所屬監(jiān)聽(tīng)
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy];
self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))];
[coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
serializer.queryStringSerialization = self.queryStringSerialization;
return serializer;
}
@end
#pragma mark - 請(qǐng)求體
//請(qǐng)求體拼接
static NSString * AFCreateMultipartFormBoundary() {
return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}
static NSString * const kAFMultipartFormCRLF = @"\r\n";
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}
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;
}
}
NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;
NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
/**
單個(gè)請(qǐng)求體文件
*/
@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (nonatomic, strong) NSDictionary *headers;//請(qǐng)求頭
@property (nonatomic, copy) NSString *boundary;//邊界
@property (nonatomic, strong) id body;//內(nèi)容
@property (nonatomic, assign) unsigned long long bodyContentLength;//內(nèi)容大小
@property (nonatomic, strong) NSInputStream *inputStream;//流
@property (nonatomic, assign) BOOL hasInitialBoundary;//是否有初始邊界
@property (nonatomic, assign) BOOL hasFinalBoundary;//是否有結(jié)束邊界
@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;//body是否存在
@property (readonly, nonatomic, assign) unsigned long long contentLength;//長(zhǎng)度
//讀取數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length;
@end
//body文件整合工具
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;//包大小
@property (nonatomic, assign) NSTimeInterval delay;//延遲
@property (nonatomic, strong) NSInputStream *inputStream;//輸入流
@property (readonly, nonatomic, assign) unsigned long long contentLength;//內(nèi)容大小
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;//是否為空
//初始化以及編碼方式
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;
//設(shè)置請(qǐng)求體數(shù)據(jù)片段的初始邊界和結(jié)束邊界
- (void)setInitialAndFinalBoundaries;
//追加請(qǐng)求體數(shù)據(jù)
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
@end
#pragma mark -
//數(shù)據(jù)和request連接器
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;//請(qǐng)求request
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;//編碼
@property (readwrite, nonatomic, copy) NSString *boundary;//邊界
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body文件整合工具
@end
@implementation AFStreamingMultipartFormData
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding
{
self = [super init];
if (!self) {
return nil;
}
self.request = urlRequest;
self.stringEncoding = encoding;
self.boundary = AFCreateMultipartFormBoundary();
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __autoreleasing *)error
{
NSParameterAssert(fileURL);
NSParameterAssert(name);
//https://www.baidu.com/abc.html 結(jié)果就是abc.html
NSString *fileName = [fileURL lastPathComponent];
//文件類型
NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}
#pragma mark - 將文件添加進(jìn)請(qǐng)求體
- (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;
}
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
//請(qǐng)求頭
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
//創(chuàng)建請(qǐng)求體模型、加入bodyStream準(zhǔn)備處理
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;
}
#pragma mark - 將數(shù)據(jù)流添加進(jìn)請(qǐng)求體
- (void)appendPartWithInputStream:(NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
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"];
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];
}
#pragma mark - 通過(guò)二進(jìn)制文件設(shè)置請(qǐng)求體
- (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];
}
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSParameterAssert(name);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
[self appendPartWithHeaders:mutableHeaders body:data];
}
- (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;
}
#pragma mark - 將請(qǐng)求體數(shù)據(jù)接入請(qǐng)求
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
//如果body為空、則直接返回request
return self.request;
}
// Reset the initial and final boundaries to ensure correct Content-Length
[self.bodyStream setInitialAndFinalBoundaries];
//將請(qǐng)求體接入request
[self.request setHTTPBodyStream:self.bodyStream];
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
[self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
return self.request;
}
@end
-
NSInputStream和NSOutputSteam的使用
AFN的這個(gè)方法可以將請(qǐng)求體中的文件、剝離出來(lái)。具體怎么剝離呢?
就是將NSInputStream對(duì)象的內(nèi)容轉(zhuǎn)寫(xiě)到NSOutputSteam
和之前AFStreamingMultipartFormData的轉(zhuǎn)寫(xiě)方法相比、少了很多判斷、是最干凈的案例了。
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(void (^)(NSError *error))handler
{
//是否有HTTPBodyStream
NSParameterAssert(request.HTTPBodyStream);
//是否是個(gè)文件
NSParameterAssert([fileURL isFileURL]);
//獲取寫(xiě)入對(duì)象
NSInputStream *inputStream = request.HTTPBodyStream;
// 使用outputStream將HTTPBodyStream的內(nèi)容寫(xiě)入到路徑為fileURL的文件中
NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
__block NSError *error = nil;
//異步寫(xiě)入
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//開(kāi)啟讀寫(xiě)工具
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
//讀取數(shù)據(jù) `hasBytesAvailable`代表是否有可用字節(jié)
while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
//將數(shù)據(jù)讀取到buffer、每次最大讀取1024
NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
if (inputStream.streamError || bytesRead < 0) {
error = inputStream.streamError;
break;
}
//將buffer寫(xiě)入
NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (outputStream.streamError || bytesWritten < 0) {
error = outputStream.streamError;
break;
}
//如果讀取與寫(xiě)入都為0、代表轉(zhuǎn)寫(xiě)結(jié)束
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
[outputStream close];
[inputStream close];
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
});
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.HTTPBodyStream = nil;
return mutableRequest;
}
當(dāng)然、這個(gè)HttpBodySteam必須是通過(guò)AFN生成的AFMultipartBodyStream實(shí)例
才會(huì)執(zhí)行這個(gè)代理方法
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
//總大小
NSInteger totalNumberOfBytesRead = 0;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//遍歷讀取數(shù)據(jù)
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
//body不存在或者沒(méi)有可讀字節(jié)
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
//把下一個(gè)body文件賦值給當(dāng)前body
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
//存在可讀數(shù)據(jù)
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
//把當(dāng)前body讀取到buffer中。內(nèi)部會(huì)采用遞歸的方式將數(shù)據(jù)分段寫(xiě)入buffer
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
#pragma clang diagnostic pop
return totalNumberOfBytesRead;
}
APIDemo
說(shuō)實(shí)話兩個(gè)文件加起來(lái)兩千多行、每行都想顧及到我自己都不知道怎么寫(xiě)...但是把很多代碼都進(jìn)行了注釋、有興趣可以自取:
GitHub
最后
本文主要是自己的學(xué)習(xí)與總結(jié)。如果文內(nèi)存在紕漏、萬(wàn)望留言斧正。如果不吝賜教小弟更加感謝。
參考資料
AFNetworking源碼 - Multipart協(xié)議,AFURLRequestSerialization和AFURLResponseSerialization
iOS開(kāi)發(fā)筆記之基于鍵值的觀察者模式(KVO)
AFNetworking 3.0 源碼解讀(三)之 AFURLRequestSerialization
HTTP請(qǐng)求報(bào)文(請(qǐng)求行、請(qǐng)求頭、請(qǐng)求體)
HTTP協(xié)議之multipart/form-data請(qǐng)求分析