AFNetWorking分析<二>

作者: wuyouyee | 来源:发表于2016-04-10 00:19 被阅读394次

    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开发工程师必需学习研究的著名框架之一。

    相关文章

      网友评论

        本文标题:AFNetWorking分析<二>

        本文链接:https://www.haomeiwen.com/subject/klymlttx.html