iOS原始碼解析—AFNetworking(RequestSerializer)
概述
AFNetworking作為著名的網路請求框架,被開發者廣泛運用。自從AF3.x開始,廢棄了之前NSURLConnection的版本,使用NSURLSession,但是之前的AF2.x版本仍然有許多值得學習的地方。以2.6.2版本為例,本系列將對該框架進行全面的學習和分析。AFNetworking框架主要包含網路通訊、序列化/反序列化、網路效能監聽、網路通訊安全四個模組,本文主要講解AFURLRequestSerialization相關類。
初始化
AFHTTPRequestSerializer是用於構建NSURLRequest的類,通過-init:方法進行初始化,指定編碼方式為NSUTF-8。HTTP請求分為請求頭和請求體,請求頭主要包含以下欄位:
User-Agent、Pragma、Content-Type、Content-Length、Accept-Language、Accept、Accept-Encoding、Cookie
init方法,指定了請求頭的Accept-Language,根據版本號、作業系統版本、裝置螢幕scale等資訊生成User-Agent,然後設定一些引數,如HTTPMethodsEncodingParametersInURI包含了指定的HTTP method,mutableObservedChangedKeyPaths存放監聽到的屬性,同時開始kvo監聽。部分程式碼註釋如下:
- (instancetype)init {
self.stringEncoding = NSUTF8StringEncoding; //UTF-8
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; //請求頭配置
...
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; //設定Accept-Language欄位
...
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];//設定User-Agent欄位
...
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; //包含指定的http method
self.mutableObservedChangedKeyPaths = [NSMutableSet set]; //kvo屬性集合
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { //kvo監聽部分屬性
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
AFHTTPRequestSerializer重寫了屬性的set方法,手動實現kvo通知,這樣當呼叫這些set方法時,會監聽到這些屬性值的改變,存入mutableObservedChangedKeyPaths中,例如:
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
下面開始構建NSURLRequest,構建的型別主要有兩種,普通型別和multipart型別。首先看一下構建普通型別的方法。
普通型別
通過-requestWithMethod:URLString:parameters:error方法構建NSURLRequest,下面是程式碼註釋:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString]; //構建URL
NSParameterAssert(url);
//根據url建立URLRequest物件
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method; //http method
//監聽的發生變化的屬性,設定為http的請求頭
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//進一步處理NSURLRequest
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
首先建立NSURLRequest物件,然後呼叫-requestBySerializingRequest:withParameters:error:方法根據引數進一步設定NSURLRequest物件,下面是程式碼註釋:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
...
//構建請求頭引數
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
//自定義方法,構建query
query = self.queryStringSerialization(request, parameters, &serializationError);
...
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//按照一定規則構建query
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query) { //將query拼在url後面
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
...
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//query設定為httpBody
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
該方法首先設定http請求的head引數,然後將parameters轉化為url的query,通過AFQueryStringFromParameters方法返回一個字串,格式是:"key1=value1&key2=value2",程式碼註釋如下:
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]]; //url encode,返回key=value格式的字串
}
return [mutablePairs componentsJoinedByString:@"&"];
}
思路是首先根據AFQueryStringPairsFromDictionary方法的會根據parameters建立一個陣列,無論parameters是否巢狀,建立後的陣列中的元素在同一層級。陣列中的元素是AFQueryStringPair物件,包裝了key/value,然後呼叫URLEncodedStringValue方法將pair物件轉化為key=value格式的字串,且key和value都做了encode。最後將返回的字元傳用"&"符號連線,生成query字串。
接著判斷當前http請求的method是否包含在HTTPMethodsEncodingParametersInURI中,如果是GET請求,則包含在內,會將生成的將query拼在url後面,因為GET請求不設定http的request body。如果是POST請求,則將query字串序列化成nsdata後,設定為http的request body。
另外,有兩個子類繼承了AFHTTPRequestSerializer類,即AFJSONRequestSerializer和AFPropertyListRequestSerializer,都重寫了父類的方法,下面看一下AFJSONRequestSerializer的方法:
- (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];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
//設定http請求頭引數
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
//parameters,設定http請求體
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
}
return mutableRequest;
}
分析該方法,當http的method是POST型別時,AFJSONRequestSerializer和父類的處理不同,該方法將parameters序列化為JSON資料,設定為http的請求體,且請求頭的"Content-Type"引數設定為"application/json"。AFPropertyListRequestSerializer的方法如下:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
...
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
}
return mutableRequest;
}
該方法邏輯類似,當http的method是POST型別時,呼叫NSPropertyListSerialization的方法將parameters序列化成NSData,賦值給http的請求體。
multipart型別
Multipart是HTTP協議為web表單新增的上傳檔案的協議,基於POST方法,資料放在http請求體,不同於普通post請求的key/value格式或者,json格式,multipart型別的請求體較長,且遵循一定的格式,下面是格式:
--BoundaryAaB03x
content-disposition: form-data; name="name"
//空行
abcdef...
--BoundaryAaB03x
content-disposition: form-data; name=”pic”; filename=“content.txt”
Content-Type: text/plain
//空行
...contents of abc.txt...
--BoundaryAaB03x
content-disposition: form-data; name=”pic”; filename=“content.txt”
Content-Type: text/plain
//空行
...contents of abc.txt...
--BoundaryAaB03x--
可以看出請求的資料可以分成若干個段落,每個段落由分隔符隔開,且頂部和中間的分隔符前面有"--",底部的分隔符前後都有"--",每個段落的格式固定如下:
頭部欄位
//空行
資料實體
下面來分析一下multipart型別的請求方法:
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
//不支援GET、HEAD型別
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
//建立NSURLRequest物件
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
//建立AFStreamingMultipartFormData物件
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
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) {
//formData物件追加引數
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
//formData物件追加引數
if (block) {
block(formData);
}
//設定request header、request body
return [formData requestByFinalizingMultipartFormData];
}
首先排除了GET和HEAD型別的請求,然後根據url建立NSURLRequest物件,然後建立建立AFStreamingMultipartFormData物件,該物件負責為NSURLRequest物件拼接請求體資料,下面主要分析AFStreamingMultipartFormData物件的結構和方法:
@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request; //URLRequest
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; //字串編碼
@property (readwrite, nonatomic, copy) NSString *boundary; //邊界標識
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body資料
@end
//初始化
- (id)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;
}
AFStreamingMultipartFormData內部維護了一個NSMutableURLRequest物件,編碼方式(字串轉成NAData的編碼方式),邊界字串和AFMultipartBodyStream物件,其中AFMultipartBodyStream物件負責維護請求體的各個資料段落。下面是該物件的定義註釋:
@interface AFMultipartBodyStream () <NSCopying>
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
@end
其中HTTPBodyParts是一個陣列,陣列中每個元素是一個AFHTTPBodyPart物件,AFHTTPBodyPart物件封裝了請求體資料中每個段落的資訊,程式碼註釋如下:
@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding; //編碼方式
@property (nonatomic, strong) NSDictionary *headers; //段落頭部資訊
@property (nonatomic, copy) NSString *boundary; //邊界字串
@property (nonatomic, strong) id body; //段落體資料
@property (nonatomic, assign) unsigned long long bodyContentLength; //段落體資料長度
@property (nonatomic, strong) NSInputStream *inputStream; //資料流,用於讀取body內如進buffer
@property (nonatomic, assign) BOOL hasInitialBoundary; //是否是開始邊界
@property (nonatomic, assign) BOOL hasFinalBoundary; //是否是結束邊界
...
@end
-multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法接下來將parameters引數轉化成AFQueryStringPair物件陣列,將每個pair物件的key和value新增到formData物件維護的段落陣列中。formData支援三種格式的資料,即NData、FileURL和NSInputStream,下面依次分析一下:
-
NSData格式:
例如-appendPartWithFormData:name:方法和-appendPartWithFileData:name:fileName:mimeType:方法,首先拼裝段落頭部資訊,設定Content-Disposition欄位和Content-Type欄位,然後將data作為body構建AFHTTPBodyPart物件,新增進bodyStream中的段落陣列HTTPBodyParts中。下面是程式碼註釋:
- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)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]; //mutableHeaders和data包裝成AFHTTPBodyPart物件,加入HTTPBodyParts陣列中 }
-
FileURL格式:
例如-appendPartWithFileURL:name:fileName:mimeType:error:方法,首先構建段落頭部資訊,設定Content-Disposition欄位和Content-Type欄位,然後構建AFHTTPBodyPart物件,body屬性是fileURL,即本地檔案路徑,bodyContentLength屬性是檔案的大小,最後新增進HTTPBodyParts中。下面是程式碼註釋:
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError * __autoreleasing *)error { ... NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; //段落頭部headers AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; //建立AFHTTPBodyPart物件 bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = fileURL; //body屬性是檔案路徑 bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue]; [self.bodyStream appendHTTPBodyPart:bodyPart]; //新增進HTTPBodyParts中 return YES; }
-
NSInputStream格式:
例如-appendPartWithInputStream:name:fileName:length:mimeType:方法,首先構建段落資訊,設定Content-Disposition欄位和Content-Type欄位,然後構建AFHTTPBodyPart物件,body屬性是NSInputStream物件,新增進HTTPBodyParts中。下面是程式碼註釋:
- (void)appendPartWithInputStream:(NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType { ... NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"];//段落頭部headers AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; //建立AFHTTPBodyPart物件 bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = inputStream; //body屬性是NSInputStream bodyPart.bodyContentLength = (unsigned long long)length; [self.bodyStream appendHTTPBodyPart:bodyPart]; //新增進HTTPBodyParts中 }
以上3種格式的資料新增進HTTPBodyParts中時,在接下來的過程中,都會轉化二進位制資料拼裝在http的請求體報文中。
-multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法接下來呼叫block(formData)供外界呼叫層繼續新增資料,方式可以使以上3種中的任意一種。然後呼叫-requestByFinalizingMultipartFormData方法為NSURLRequest物件新增請求報文的頭,因為採用multipart型別的報文格式,所以設定Content-Type為"multipart/form-data",下面是程式碼註釋:
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
if ([self.bodyStream isEmpty]) {
return self.request;
}
//設定出初始和結尾的邊界
[self.bodyStream setInitialAndFinalBoundaries];
//將bodyStream作為請求報文體
[self.request setHTTPBodyStream:self.bodyStream];
//設定請求頭的Content-Type和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;
}
其中self.bodyStream屬性是AFMultipartBodyStream型別,繼承於NSInputStream,將該屬性設定為http的HTTPBodyStream屬性,這樣在傳送http請求時,會按照檔案流的方式傳送請求報文,且上傳資料是分片的,不會一次性將報文體資料讀入記憶體中後傳送,關於這種方式的比較,可以參考JSPatch大神bang的文章。
接下來通過NSURLSession傳送請求時,呼叫-uploadTaskWithStreamedRequest:方法構建NSURLSessionUploadTask物件uploadTask,呼叫resume開始傳送請求。由於已經設定bodyStream為NSInputStream物件,這種方式傳送請求時會讀取bodyStream的資料,呼叫NSInputStream物件的-read:maxLength:方法,而AFMultipartBodyStream物件重寫了-read:maxLength:方法,程式碼註釋如下:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
...
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
#pragma clang diagnostic pop
return totalNumberOfBytesRead;
}
該方法遍歷HTTPBodyParts陣列中的各個元素,其中HTTPBodyPartEnumerator是列舉器,依附於HTTPBodyParts陣列,呼叫nextObject方法取出陣列中的物件作為當前物件currentHTTPBodyPart。然後呼叫AFHTTPBodyPart的-read:maxLength:方法將AFHTTPBodyPart物件中的各個部分讀入buffer中。下面是程式碼註釋:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
if (_phase == AFEncapsulationBoundaryPhase) { //邊界部分
...
}
if (_phase == AFHeaderPhase) { //header部分
...
}
if (_phase == AFBodyPhase) { //body部分
...
}
if (_phase == AFFinalBoundaryPhase) { //尾部邊界
...
}
return totalNumberOfBytesRead;
}
通過一組列舉型別AFHTTPBodyPartReadPhase表示段落中的不同部分,依次將它們生成報文資料,然後讀入buffer中,生成資料的時候需要按照rfc1867規定,在相應地方新增\r\n換行符。當前階段讀完資料後呼叫-transitionToNextPhase方法進入下一個部分。需要注意的是,_phase==AFBodyPhase階段時,通過self.inputStream物件讀取資料進buffer中,self.inputStream是內部持有的一個屬性,body中的資料由inputStream負責載入,通過懶載入的方式初始化:
- (NSInputStream *)inputStream {
if (!_inputStream) {
if ([self.body isKindOfClass:[NSData class]]) {
_inputStream = [NSInputStream inputStreamWithData:self.body];
} else if ([self.body isKindOfClass:[NSURL class]]) {
_inputStream = [NSInputStream inputStreamWithURL:self.body];
} else if ([self.body isKindOfClass:[NSInputStream class]]) {
_inputStream = self.body;
} else {
_inputStream = [NSInputStream inputStreamWithData:[NSData data]];
}
}
return _inputStream;
}
inputStream會根據之前存入AFHTTPBodyPart物件中的body資料型別來初始化inputStream,說明之前3種格式的資料最終都會轉化成NSInputStream物件inputStream,然後讀取inputStream就可以。關於讀取body資料進buffer的程式碼註釋如下:
if (_phase == AFBodyPhase) {
NSInteger numberOfBytesRead = 0;
//從inputStream中讀取資料
numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
if (numberOfBytesRead == -1) {
return -1;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
[self transitionToNextPhase];
}
}
}
totalNumberOfBytesRead表示總共讀取的位元組數。AFMultipartBodyStream物件通過重寫父類的-read:maxLength:方法實現了拼接請求報文的body資料。
相關文章
- AFNetworking原始碼解析系列(1)原始碼
- AFNetworking原始碼解析系列(2)原始碼
- 原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking原始碼UIWebView
- 原始碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking原始碼UI
- 原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking原始碼UIView
- 原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking原始碼UIView
- 原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking原始碼UI
- 原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking原始碼UIIndicatorView
- AFNetworking 原始碼分析(一)原始碼
- routable-ios原始碼解析iOS原始碼
- [iOS]JPVideoPlayer 3.0 原始碼解析iOSIDE原始碼
- 原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization原始碼
- 原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager原始碼IndicatorORM
- 原始碼閱讀:AFNetworking(九)——AFImageDownloader原始碼
- 原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache原始碼
- 原始碼閱讀:AFNetworking(六)——AFURLSessionManager原始碼Session
- 原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager原始碼HTTPSession
- iOS 第三方AFNetworkingiOS
- iOS開發 面向切面程式設計之 Aspects 原始碼解析iOS程式設計原始碼
- Java Timer原始碼解析(定時器原始碼解析)Java原始碼定時器
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- ReactNative原始碼解析-初識原始碼React原始碼
- Koa 原始碼解析原始碼
- Koa原始碼解析原始碼
- RxPermission原始碼解析原始碼
- Express原始碼解析Express原始碼
- redux原始碼解析Redux原始碼
- CopyOnWriteArrayList原始碼解析原始碼
- LeakCanary原始碼解析原始碼
- ArrayBlockQueue原始碼解析BloC原始碼
- ReentrantLock原始碼解析ReentrantLock原始碼
- OKio原始碼解析原始碼
- ReentrantReadWriteLock原始碼解析原始碼
- CyclicBarrier原始碼解析原始碼
- Semaphore原始碼解析原始碼
- Exchanger原始碼解析原始碼
- SDWebImage原始碼解析Web原始碼
- AbstractQueuedSynchronizer原始碼解析原始碼
- LinkedList原始碼解析原始碼