AFHTTPSessionManager
通常我們在運用AFN框架進行網(wǎng)絡(luò)請求時,使用的都是AFHTTPSessionManager
這個類。AFHTTPSessionManager
繼承于AFURLSessionManager
,是對網(wǎng)絡(luò)請求的進一步封裝。這個類將繁瑣的配置request
、拼接formdata等工作進行了封裝,僅僅提供GET
、POST
、HEAD
、PUT
、DELETE
這幾個非常方便直觀的API。
AFHTTPSessionManager
相對于其父類,新添加了三個屬性baseURL
、requestSerializer
、responseSerializer
。
請求器
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
AFHTTPRequestSerializer
這個類就是AFN框架對于網(wǎng)絡(luò)請求中request
配置的封裝,它服從AFURLRequestSerialization
協(xié)議,之后會詳細講解。在使用AFHTTPSessionManager
時候,這個屬性是不能為nil
的,在它的init
方法中,是初始化為[AFHTTPRequestSerializer serializer]
,當(dāng)然也可以自己改變這個請求器,這個取決于你的后臺要接受什么類型的數(shù)據(jù),如果你的后臺是要接收json格式的請求那么就是[AFJSONRequestSerializer serializer]
。
響應(yīng)器
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
AFHTTPResponseSerializer
這個類是對網(wǎng)絡(luò)請求響應(yīng)的封裝,通過改變這個屬性,AFN框架可以自動對請求下來的數(shù)據(jù)進行解析。例如,你請求下來的數(shù)據(jù)是json格式,那么將responseSerializer
賦值成[AFJSONResponseSerializer serializer]
,于是你得到就是解析后的數(shù)據(jù)。這里要注意,如果在請求時出現(xiàn)3840的錯誤碼,那就是你的responseSerializer
有問題,很有可能請求下來的不是json串,而你指定它要json解析。
因為我們平常開發(fā)常用到請求方式一般是兩種:GET
、POST
。所以,我就以這兩個請求方式為例。
通常我們使用+ (instancetype)manager
類方法,以此調(diào)用初始化方法
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
在這里看到init
方法,對請求器與響應(yīng)器進行了初始化賦值。
之后我們會調(diào)用例如下面的方法
GET方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
在這個方法內(nèi),調(diào)用
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
得到dataTask
之后執(zhí)行resume
方法。在這個方法里,首先會根據(jù)我們進行網(wǎng)絡(luò)請求的方法來配置request
。這時就用到了AFHTTPSessionManager
的requestSerializer
屬性,通過屬性中的值來調(diào)用類的實例方法,之后,判斷是否配置錯誤,如果配置錯誤,則通過GCD在completionQueue
這個隊列中會調(diào)出錯誤信息,這里如果你沒有對這個屬性進行賦值的話,它會選擇在主線程回調(diào)錯誤信息。配置好request
之后就調(diào)用父類的網(wǎng)絡(luò)請求的方法,得到dataTask
返回,在請求完成時,執(zhí)行success或者failure的block。注意,這里得到的dataTask
需要回調(diào)給外部,所以需要__block
修飾。
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
在這些提供給外界使用的api里,有一個api是特殊的
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
這個方法是我們進行網(wǎng)絡(luò)上傳時使用的方法,我們可以看到它多加入了一個block參數(shù),這個block中有一個服從AFMultipartFormData
協(xié)議的參數(shù)formData
,從字面上我們就可以知道,如果我們需要上傳什么數(shù)據(jù)的話只需要往這個參數(shù)后面進行拼接就可以了,事實上也的確如此。在這個POST
方法中,調(diào)用了requestSerializer
的另外一個用來配置上傳文件的request的方法。
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
AFURLRequestSerialization
中,構(gòu)建Multipart請求是占篇幅很大的一個功能,它也的確值得耗費更多的代碼。在上一章,我已經(jīng)講了在iOS設(shè)備上傳文件時是multipart協(xié)議上傳,所以需要按照格式進行配置request,這里就不在贅述了。在用NSURLRequest
上傳文件時,一般是兩種方法,一個是設(shè)置body,但是如果文件稍大的話,將會撐爆內(nèi)存。另外一種則是,創(chuàng)建一個臨時文件,將數(shù)據(jù)拼接進去,然后將文件路徑設(shè)置為bodyStream,這樣就可以分片的上傳了。而AFN框架則是更進一步的運用邊傳邊拼的方式上傳文件,這無疑是更加高端也是更加繁瑣的方法。
這里通過constructingBodyWithBlock
向使用者提供了一個AFStreamingMultipartFormData
對象,調(diào)這個對象的append方法, AFStreamingMultipartFormData
內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的AFHTTPBodyPart
,添加到自定義的 AFMultipartBodyStream
里。最后把AFMultipartBodyStream
賦給原來NSMutableURLRequest
的bodyStream。NSURLConnection
發(fā)送請求時會讀取這個 bodyStream,在讀取數(shù)據(jù)時會調(diào)用這個 bodyStream 的 -read:maxLength:
方法,AFMultipartBodyStream
重寫了這個方法,不斷讀取之前 append進來的AFHTTPBodyPart
數(shù)據(jù)直到讀完。
- (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"
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;
}
下圖就是multipart方式進行上傳文件的request配置的步驟圖。
AFMultipartBodyStream
AFMultipartBodyStream
是NSInputStream
的子類,有人覺得是不是只要簡單的將這個類setHTTPBodyStream
給request就可以了?事實上并不是這樣,用NSURLRequest 發(fā)出請求會導(dǎo)致 crash,提示
[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector
這是因為NSURLRequest
實際上接受的不是NSInputStream
對象,而是 CoreFoundation 的 CFReadStreamRef
對象,因為 CFReadStreamRef
和NSInputStream
是 toll-free bridged,可以自由轉(zhuǎn)換,但CFReadStreamRef
會用到 CFStreamScheduleWithRunLoop
這個方法,當(dāng)它調(diào)用到這個方法時,object-c 的 toll-free bridging 機制會調(diào)用 object-c 對象 NSInputStream
的相應(yīng)函數(shù),這里就調(diào)用到了_scheduleInCFRunLoop:forMode:
,若不實現(xiàn)這個方法就會crash。是不是覺得好繞???的確,在學(xué)習(xí)這套框架的時候,我不停在的感慨大神就是大神,給你一種非常完美的感覺。其實AFN框架絕不僅僅是只有這幾個重點,剩下的東西還有很多很多,例如還有AFURLResponseSerialization
,和網(wǎng)絡(luò)請求驗證證書的A'FSecurityPolicy
。整體的架構(gòu)真的很漂亮,絕對是iOS開發(fā)工程師必需學(xué)習(xí)研究的著名框架之一。