AFHTTPSessionManager
通常我們在運(yùn)用AFN框架進(jìn)行網(wǎng)絡(luò)請求時(shí),使用的都是AFHTTPSessionManager
這個(gè)類沟于。AFHTTPSessionManager
繼承于AFURLSessionManager
,是對網(wǎng)絡(luò)請求的進(jìn)一步封裝兴猩。這個(gè)類將繁瑣的配置request
旁趟、拼接formdata等工作進(jìn)行了封裝,僅僅提供GET
皮假、POST
呛梆、HEAD
搪泳、PUT
、DELETE
這幾個(gè)非常方便直觀的API姿现。
AFHTTPSessionManager
相對于其父類,新添加了三個(gè)屬性baseURL
肠仪、requestSerializer
、responseSerializer
备典。
請求器
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
AFHTTPRequestSerializer
這個(gè)類就是AFN框架對于網(wǎng)絡(luò)請求中request
配置的封裝,它服從AFURLRequestSerialization
協(xié)議,之后會(huì)詳細(xì)講解藤韵。在使用AFHTTPSessionManager
時(shí)候,這個(gè)屬性是不能為nil
的,在它的init
方法中,是初始化為[AFHTTPRequestSerializer serializer]
,當(dāng)然也可以自己改變這個(gè)請求器,這個(gè)取決于你的后臺(tái)要接受什么類型的數(shù)據(jù),如果你的后臺(tái)是要接收json格式的請求那么就是[AFJSONRequestSerializer serializer]
。
響應(yīng)器
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
AFHTTPResponseSerializer
這個(gè)類是對網(wǎng)絡(luò)請求響應(yīng)的封裝,通過改變這個(gè)屬性,AFN框架可以自動(dòng)對請求下來的數(shù)據(jù)進(jìn)行解析熊经。例如,你請求下來的數(shù)據(jù)是json格式,那么將responseSerializer
賦值成[AFJSONResponseSerializer serializer]
,于是你得到就是解析后的數(shù)據(jù)泽艘。這里要注意,如果在請求時(shí)出現(xiàn)3840的錯(cuò)誤碼,那就是你的responseSerializer
有問題,很有可能請求下來的不是json串,而你指定它要json解析。
因?yàn)槲覀兤匠i_發(fā)常用到請求方式一般是兩種:GET
镐依、POST
匹涮。所以,我就以這兩個(gè)請求方式為例。
通常我們使用+ (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)器進(jìn)行了初始化賦值槐壳。
之后我們會(huì)調(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;
}
在這個(gè)方法內(nèi),調(diào)用
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
得到dataTask
之后執(zhí)行resume
方法然低。在這個(gè)方法里,首先會(huì)根據(jù)我們進(jìn)行網(wǎng)絡(luò)請求的方法來配置request
。這時(shí)就用到了AFHTTPSessionManager
的requestSerializer
屬性,通過屬性中的值來調(diào)用類的實(shí)例方法,之后,判斷是否配置錯(cuò)誤,如果配置錯(cuò)誤,則通過GCD在completionQueue
這個(gè)隊(duì)列中會(huì)調(diào)出錯(cuò)誤信息,這里如果你沒有對這個(gè)屬性進(jìn)行賦值的話,它會(huì)選擇在主線程回調(diào)錯(cuò)誤信息务唐。配置好request
之后就調(diào)用父類的網(wǎng)絡(luò)請求的方法,得到dataTask
返回,在請求完成時(shí),執(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里,有一個(gè)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
這個(gè)方法是我們進(jìn)行網(wǎng)絡(luò)上傳時(shí)使用的方法,我們可以看到它多加入了一個(gè)block參數(shù),這個(gè)block中有一個(gè)服從AFMultipartFormData
協(xié)議的參數(shù)formData
,從字面上我們就可以知道,如果我們需要上傳什么數(shù)據(jù)的話只需要往這個(gè)參數(shù)后面進(jìn)行拼接就可以了,事實(shí)上也的確如此枫笛。在這個(gè)POST
方法中,調(diào)用了requestSerializer
的另外一個(gè)用來配置上傳文件的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請求是占篇幅很大的一個(gè)功能,它也的確值得耗費(fèi)更多的代碼。在上一章,我已經(jīng)講了在iOS設(shè)備上傳文件時(shí)是multipart協(xié)議上傳,所以需要按照格式進(jìn)行配置request,這里就不在贅述了刑巧。在用NSURLRequest
上傳文件時(shí),一般是兩種方法,一個(gè)是設(shè)置body,但是如果文件稍大的話,將會(huì)撐爆內(nèi)存喧兄。另外一種則是,創(chuàng)建一個(gè)臨時(shí)文件,將數(shù)據(jù)拼接進(jìn)去,然后將文件路徑設(shè)置為bodyStream,這樣就可以分片的上傳了。而AFN框架則是更進(jìn)一步的運(yùn)用邊傳邊拼的方式上傳文件,這無疑是更加高端也是更加繁瑣的方法啊楚。
這里通過constructingBodyWithBlock
向使用者提供了一個(gè)AFStreamingMultipartFormData
對象吠冤,調(diào)這個(gè)對象的append方法, AFStreamingMultipartFormData
內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的AFHTTPBodyPart
,添加到自定義的 AFMultipartBodyStream
里恭理。最后把AFMultipartBodyStream
賦給原來NSMutableURLRequest
的bodyStream拯辙。NSURLConnection
發(fā)送請求時(shí)會(huì)讀取這個(gè) bodyStream,在讀取數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè) bodyStream 的 -read:maxLength:
方法颜价,AFMultipartBodyStream
重寫了這個(gè)方法涯保,不斷讀取之前 append進(jìn)來的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方式進(jìn)行上傳文件的request配置的步驟圖。
AFMultipartBodyStream
AFMultipartBodyStream
是NSInputStream
的子類,有人覺得是不是只要簡單的將這個(gè)類setHTTPBodyStream
給request就可以了?事實(shí)上并不是這樣,用NSURLRequest 發(fā)出請求會(huì)導(dǎo)致 crash遭赂,提示
[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector
這是因?yàn)?code>NSURLRequest實(shí)際上接受的不是NSInputStream
對象循诉,而是 CoreFoundation 的 CFReadStreamRef
對象,因?yàn)?CFReadStreamRef
和NSInputStream
是 toll-free bridged撇他,可以自由轉(zhuǎn)換茄猫,但CFReadStreamRef
會(huì)用到 CFStreamScheduleWithRunLoop
這個(gè)方法,當(dāng)它調(diào)用到這個(gè)方法時(shí)困肩,object-c 的 toll-free bridging 機(jī)制會(huì)調(diào)用 object-c 對象 NSInputStream
的相應(yīng)函數(shù)划纽,這里就調(diào)用到了_scheduleInCFRunLoop:forMode:
,若不實(shí)現(xiàn)這個(gè)方法就會(huì)crash锌畸。是不是覺得好繞坝铝印?的確,在學(xué)習(xí)這套框架的時(shí)候,我不停在的感慨大神就是大神,給你一種非常完美的感覺潭枣。其實(shí)AFN框架絕不僅僅是只有這幾個(gè)重點(diǎn),剩下的東西還有很多很多,例如還有AFURLResponseSerialization
,和網(wǎng)絡(luò)請求驗(yàn)證證書的A'FSecurityPolicy
比默。整體的架構(gòu)真的很漂亮,絕對是iOS開發(fā)工程師必需學(xué)習(xí)研究的著名框架之一。