AFNetworking分析<二>

AFHTTPSessionManager

通常我们在运用AFN框架进行网络请求时,使用的都是AFHTTPSessionManager这个类。AFHTTPSessionManager继承于AFURLSessionManager,是对网络请求的进一步封装。这个类将繁琐的配置request、拼接formdata等工作进行了封装,仅仅提供GETPOSTHEADPUTDELETE这几个非常方便直观的API。
AFHTTPSessionManager相对于其父类,新添加了三个属性baseURLrequestSerializerresponseSerializer

请求器
@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解析。
因为我们平常开发常用到请求方式一般是两种:GETPOST。所以,我就以这两个请求方式为例。
通常我们使用+ (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。这时就用到了AFHTTPSessionManagerrequestSerializer属性,通过属性中的值来调用类的实例方法,之后,判断是否配置错误,如果配置错误,则通过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

AFMultipartBodyStreamNSInputStream的子类,有人觉得是不是只要简单的将这个类setHTTPBodyStream给request就可以了?事实上并不是这样,用NSURLRequest 发出请求会导致 crash,提示

[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector

这是因为NSURLRequest实际上接受的不是NSInputStream 对象,而是 CoreFoundation 的 CFReadStreamRef 对象,因为 CFReadStreamRefNSInputStream 是 toll-free bridged,可以自由转换,但CFReadStreamRef 会用到 CFStreamScheduleWithRunLoop 这个方法,当它调用到这个方法时,object-c 的 toll-free bridging 机制会调用 object-c 对象 NSInputStream 的相应函数,这里就调用到了_scheduleInCFRunLoop:forMode:,若不实现这个方法就会crash。是不是觉得好绕啊?的确,在学习这套框架的时候,我不停在的感慨大神就是大神,给你一种非常完美的感觉。其实AFN框架绝不仅仅是只有这几个重点,剩下的东西还有很多很多,例如还有AFURLResponseSerialization,和网络请求验证证书的A'FSecurityPolicy。整体的架构真的很漂亮,绝对是iOS开发工程师必需学习研究的著名框架之一。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值