AFHTTPSessionManager
通常我们在运用AFN框架进行网络请求时,使用的都是AFHTTPSessionManager
这个类。AFHTTPSessionManager
继承于AFURLSessionManager
,是对网络请求的进一步封装。这个类将繁琐的配置request
、拼接formdata等工作进行了封装,仅仅提供GET
、POST
、HEAD
、PUT
、DELETE
这几个非常方便直观的API。
AFHTTPSessionManager
相对于其父类,新添加了三个属性baseURL
、requestSerializer
、responseSerializer
。
请求器
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
AFHTTPRequestSerializer
这个类就是AFN框架对于网络请求中request
配置的封装,它服从AFURLRequestSerialization
协议,之后会详细讲解。在使用AFHTTPSessionManager
时候,这个属性是不能为nil
的,在它的init
方法中,是初始化为[AFHTTPRequestSerializer serializer]
,当然也可以自己改变这个请求器,这个取决于你的后台要接受什么类型的数据,如果你的后台是要接收json格式的请求那么就是[AFJSONRequestSerializer serializer]
。
响应器
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
AFHTTPResponseSerializer
这个类是对网络请求响应的封装,通过改变这个属性,AFN框架可以自动对请求下来的数据进行解析。例如,你请求下来的数据是json格式,那么将responseSerializer
赋值成[AFJSONResponseSerializer serializer]
,于是你得到就是解析后的数据。这里要注意,如果在请求时出现3840的错误码,那就是你的responseSerializer
有问题,很有可能请求下来的不是json串,而你指定它要json解析。
因为我们平常开发常用到请求方式一般是两种:GET
、POST
。所以,我就以这两个请求方式为例。
通常我们使用+ (instancetype)manager
类方法,以此调用初始化方法
- (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
方法,对请求器与响应器进行了初始化赋值。
之后我们会调用例如下面的方法
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;
}
在这个方法内,调用
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
得到dataTask
之后执行resume
方法。在这个方法里,首先会根据我们进行网络请求的方法来配置request
。这时就用到了AFHTTPSessionManager
的requestSerializer
属性,通过属性中的值来调用类的实例方法,之后,判断是否配置错误,如果配置错误,则通过GCD在completionQueue
这个队列中会调出错误信息,这里如果你没有对这个属性进行赋值的话,它会选择在主线程回调错误信息。配置好request
之后就调用父类的网络请求的方法,得到dataTask
返回,在请求完成时,执行success或者failure的block。注意,这里得到的dataTask
需要回调给外部,所以需要__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
这个方法是我们进行网络上传时使用的方法,我们可以看到它多加入了一个block参数,这个block中有一个服从AFMultipartFormData
协议的参数formData
,从字面上我们就可以知道,如果我们需要上传什么数据的话只需要往这个参数后面进行拼接就可以了,事实上也的确如此。在这个POST
方法中,调用了requestSerializer
的另外一个用来配置上传文件的request的方法。
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
AFURLRequestSerialization
中,构建Multipart请求是占篇幅很大的一个功能,它也的确值得耗费更多的代码。在上一章,我已经讲了在iOS设备上传文件时是multipart协议上传,所以需要按照格式进行配置request,这里就不在赘述了。在用NSURLRequest
上传文件时,一般是两种方法,一个是设置body,但是如果文件稍大的话,将会撑爆内存。另外一种则是,创建一个临时文件,将数据拼接进去,然后将文件路径设置为bodyStream,这样就可以分片的上传了。而AFN框架则是更进一步的运用边传边拼的方式上传文件,这无疑是更加高端也是更加繁琐的方法。
这里通过constructingBodyWithBlock
向使用者提供了一个AFStreamingMultipartFormData
对象,调这个对象的append方法, AFStreamingMultipartFormData
内部把这些append的数据转成不同类型的AFHTTPBodyPart
,添加到自定义的 AFMultipartBodyStream
里。最后把AFMultipartBodyStream
赋给原来NSMutableURLRequest
的bodyStream。NSURLConnection
发送请求时会读取这个 bodyStream,在读取数据时会调用这个 bodyStream 的 -read:maxLength:
方法,AFMultipartBodyStream
重写了这个方法,不断读取之前 append进来的AFHTTPBodyPart
数据直到读完。
- (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配置的步骤图。
NSMutableURLRequest的构建步骤
AFMultipartBodyStream
AFMultipartBodyStream
是NSInputStream
的子类,有人觉得是不是只要简单的将这个类setHTTPBodyStream
给request就可以了?事实上并不是这样,用NSURLRequest 发出请求会导致 crash,提示
[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector
这是因为NSURLRequest
实际上接受的不是NSInputStream
对象,而是 CoreFoundation 的 CFReadStreamRef
对象,因为 CFReadStreamRef
和NSInputStream
是 toll-free bridged,可以自由转换,但CFReadStreamRef
会用到 CFStreamScheduleWithRunLoop
这个方法,当它调用到这个方法时,object-c 的 toll-free bridging 机制会调用 object-c 对象 NSInputStream
的相应函数,这里就调用到了_scheduleInCFRunLoop:forMode:
,若不实现这个方法就会crash。是不是觉得好绕啊?的确,在学习这套框架的时候,我不停在的感慨大神就是大神,给你一种非常完美的感觉。其实AFN框架绝不仅仅是只有这几个重点,剩下的东西还有很多很多,例如还有AFURLResponseSerialization
,和网络请求验证证书的A'FSecurityPolicy
。整体的架构真的很漂亮,绝对是iOS开发工程师必需学习研究的著名框架之一。
网友评论