iOS AFNetWorking源码详解(二)

作者: 宫城_ | 来源:发表于2016-03-15 23:51 被阅读2322次

    上一篇主要讲了下AFURLSessionManager里面的实现内容和一些知识点,这一篇来讲一些AF里面的其他类所做的事情


    AFHTTPSessionManager继承于AFURLSessionManager,提供了更方便的HTTP请求方法,包括了GET、POST、PUT、PATCH、DELETE这五种方式,并且AF鼓励我们在AFHTTPSessionManager再进行一次封装来满足我们自己的业务需求


    在开始的地方,AF一直提醒到一个变量baseURL,这个变量你可以在进一步封装的时候,将baseURL写成你自己的HTTP请求原始地址,比如

    + (NSURL *)baseURL {
        return [NSURL URLWithString:kBaseURLString];
    }
    

    在对baseURL进行拼接的时候,也需要注意一下几点,防止出现请求的URL出现问题

        NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
        [NSURL URLWithString:@"foo" relativeToURL:baseURL];                  // http://example.com/v1/foo
        [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];          // http://example.com/v1/foo?bar=baz
        [NSURL URLWithString:@"/foo" relativeToURL:baseURL];                 // http://example.com/foo
        [NSURL URLWithString:@"foo/" relativeToURL:baseURL];                 // http://example.com/v1/foo
        [NSURL URLWithString:@"/foo/" relativeToURL:baseURL];                // http://example.com/foo/
        [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/
    

    在初始化的方法里面,我们看到这个方法

    - (instancetype)initWithBaseURL:(nullable NSURL *)url
               sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
    

    NS_DESIGNATED_INITIALIZER这个是什么意思呢?
    它是表明该类有多种初始化的方法,在加上这个标记后,在系统的init方法里面一定要调用该方法

    - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                        parameters:(nullable id)parameters
                           success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                           failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
    

    DEPRECATED_ATTRIBUTE这个相信大家见得比较多了,字面意思就是这个API不建议开发者再使用了,再使用时,会出现编译警告


    下面POST、GET、PUT、PATCH、DELETE方法传参基本都是大同小异

    URLString表示请求的URL,parameters表示客户端请求内容的存储器,progress表示请求的进度,constructingBodyWithBlock里面只有一个formData用来拼接到HTTP的请求体,success表示请求成功后的block回调,failure表示请求失败的block回调

    那么这几个请求有什么区别呢?
    1、POST请求是向服务端发送数据的,用来更新资源信息,它可以改变数据的种类等资源
    2、GET请求是向服务端发起请求数据,用来获取或查询资源信息
    3、PUT请求和POST请求很像,都是发送数据的,但是PUT请求不能改变数据的种类等资源,它只能修改内容
    4、DELETE请求就是用来删除某个资源的
    5、PATCH请求和PUT请求一样,也是用来进行数据更新的,它是HTTP verb推荐用于更新的

    在实际开发过程中,我们还是使用POST和GET请求是最多的


    在请求实现的部分,都是调用了自己的一个方法

    - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                           URLString:(NSString *)URLString
                                          parameters:(id)parameters
                                      uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                    downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                             success:(void (^)(NSURLSessionDataTask *, id))success
                                             failure:(void (^)(NSURLSessionDataTask *, NSError *))failure;
    

    传参的内容基本都是和上一层方法一样,method指的就是请求的类型

    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
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            if (error) {
                if (failure) {
                    failure(dataTask, error);
                }
            } else {
                if (success) {
                    success(dataTask, responseObject);
                }
            }
        }];
    
        return dataTask;
    

    内部的实现则是先根据传入的URLString创建一个request对象,然后调用父类的dataTaskWithRequest方法生成dataTask任务,奏是这么简单


    AFNetworkReachabilityManager是监测网络状态的类,状态值有以下四种

    typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
        AFNetworkReachabilityStatusUnknown          = -1,//未知
        AFNetworkReachabilityStatusNotReachable     = 0,//不可用
        AFNetworkReachabilityStatusReachableViaWWAN = 1,//无线广域网连接
        AFNetworkReachabilityStatusReachableViaWiFi = 2,//WiFi连接
    };
    

    你可以通过域名或者socket地址来实例化对象,也可以通过创建一个SCNetworkReachabilityRef对象来初始化对象

    + (instancetype)managerForDomain:(NSString *)domain;
    + (instancetype)managerForAddress:(const void *)address;
    - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
    

    然后调用startMonitoringstopMonitoring来开始和结束监测,在中间网络状态变化的过程中,你可以通过setReachabilityStatusChangeBlock来获得网络的状态,也可以通过注册通知的形式,接收网络状态


    - (void)startMonitoring {
        [self stopMonitoring];
    
        if (!self.networkReachability) {
            return;
        }
    
        __weak __typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
    
            strongSelf.networkReachabilityStatus = status;
            if (strongSelf.networkReachabilityStatusBlock) {
                strongSelf.networkReachabilityStatusBlock(status);
            }
    
        };
    
        SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
        SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
        SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
            SCNetworkReachabilityFlags flags;
            if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
                AFPostReachabilityStatusChange(flags, callback);
            }
        });
    }
    

    在开启监测网络状态的时候,首先设置好自己网络状态的block回调,然后创建一个SCNetworkReachabilityContext结构体,第一个参数version是版本号,值为0,第二个参数info是一个指向数据block回调的c指针,第三个参数retain则是通过一个回调来再保留数据一次,它的值可能为null,第四个参数release则是通过回调移除,第五个参数description就是提供数据的描述。然后设置回调,把它加到runloop里面对它进行监测,并且在后台线程发现变化时,发送网络状态变化


    AFSecurityPolicy是安全策略类,有三种SSL Pinning模式

    typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
        AFSSLPinningModeNone,//没有安全策略
        AFSSLPinningModePublicKey,//公钥
        AFSSLPinningModeCertificate,//证书
    };
    
    @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
    

    这个是证书集合,泛型里面表示了集合里面是NSData类型,表明这个是用来存证书数据的集合,这些证书根据SSL Pinning模式来和服务器进行校验,默认是没有证书的,我们需要调用certificatesInBundle:方法将bundle里面的证书文件转成里面是data类型的集合

    + (instancetype)defaultPolicy;
    + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
    + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates;
    

    有三种实例化的方法,一种是默认策略,AFSSLPinningModeNone,第二种是自定义一个安全策略,然后获取当前类的bundle,读取cer文件生成集合,第三种则是需要我们传入证书集合


    AFURLRequestSerialization是用来对URL请求做一些处理

    将URL里面的特殊字符替换成百分号:

    FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
    

    将字典里面的key/value值组装成%@=%@并以&区分的格式:

    FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
    

    AFHTTPRequestSerializer里面有设置头信息的方法

    - (void)setValue:(nullable NSString *)value
    forHTTPHeaderField:(NSString *)field;
    

    在这里value为空时,会被当做删除处理,会删除已存在的请求头,不会空时,会增加新的请求头或者设置已存在的请求头

    - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                           password:(NSString *)password;
    

    将用户名、密码作为信息,设置为请求头,里面先将用户名密码拼好转成data,再通过base64编码的形式转成字符串,再设置为请求头的内容


    创建请求的方法有三种:

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(nullable id)parameters
                                         error:(NSError * _Nullable __autoreleasing *)error;
    - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                                  URLString:(NSString *)URLString
                                                 parameters:(nullable NSDictionary <NSString *, id> *)parameters
                                  constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                                                      error:(NSError * _Nullable __autoreleasing *)error;
    - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                                 writingStreamContentsToFile:(NSURL *)fileURL
                                           completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
    

    method指的是请求的方法,比如GET、POST等,URLString是用来创建请求URL的字符串,parameters是GET请求的查询字段,或者是请求的HTTP体,request是HTTPBodyStream里面的实例变量request,fileURL是文件URL


    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                            error:(NSError * _Nullable __autoreleasing *)error;
    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                         mimeType:(NSString *)mimeType
                            error:(NSError * _Nullable __autoreleasing *)error;
    

    根据文件数据来拼接HTTP头,fileURL是文件的URL,name指的是数据的名字,fileName是文件的名字,可以根据fileURL来获取到最后一部分的文件名,mimeType是文件数据的mime类型,可以根据文件的后缀调用AFContentTypeForPathExtension方法来获得

    static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
        NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
        NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
        if (!contentType) {
            return @"application/octet-stream";
        } else {
            return contentType;
        }
    }
    

    首先根据后缀来创建一个类型标识,然后再将类型标识转成mime类型,如果有对应的类型,则返回application/octet-stream,否则直接返回contentType

    - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                      delay:(NSTimeInterval)delay;
    

    当在3G或者E网络环境下时,在流请求时,可能会出现上传失败的情况,所以我们可以通过设置请求的带宽以及延迟时间来解决问题,numberOfBytes是字节大小,默认是16kb,delay是延迟时间,默认是没有延迟


    在实现文件里面,有一个创建多部分组成的边界符这个方法

    static NSString * AFCreateMultipartFormBoundary() {
        return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
    }
    

    这里用到一个生成随机数的方法arc4random(),oc里面还有一个生成随机数的方法random(),那这两个方法有什么区别呢?
    首先arc4random()的取值范围是0x100000000 (4294967296),random()是0x7fffffff (2147483647),前者是后者的两倍,在精度上面优于后者,而且在使用random()的时候,需要自己先生成一个随机种子,但arc4random()在第一次调用的时候就自动生成了,在使用上面也比较方便


    AFURLResponseSerialization中定义了一个协议

    - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                               data:(nullable NSData *)data
                              error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
    

    用来处理不同类型的响应解析,在里面各个类都实现了该协议


    AFJSONResponseSerializer默认接受这三个MIME类型

     - `application/json`
     - `text/json`
     - `text/javascript`
    

    AFXMLParserResponseSerializer默认接受这两个MIME类型

     - `application/xml`
     - `text/xml`
    

    AFPropertyListResponseSerializer默认接受这一个MIME类型

     - `application/x-plist`
    

    AFImageResponseSerializer默认接受这十个MIME类型

     - `image/tiff`
     - `image/jpeg`
     - `image/gif`
     - `image/png`
     - `image/ico`
     - `image/x-icon`
     - `image/bmp`
     - `image/x-bmp`
     - `image/x-xbitmap`
     - `image/x-win-bitmap`
    

    AFHTTPResponseSerializer实现文件里面

    - (BOOL)validateResponse:(NSHTTPURLResponse *)response
                        data:(NSData *)data
                       error:(NSError * __autoreleasing *)error
    {
        BOOL responseIsValid = YES;
        NSError *validationError = nil;
    
        if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
            if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
                if ([data length] > 0 && [response URL]) {
                    NSMutableDictionary *mutableUserInfo = [@{
                                                              NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                              NSURLErrorFailingURLErrorKey:[response URL],
                                                              AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                            } mutableCopy];
                    if (data) {
                        mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                    }
    
                    validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
                }
    
                responseIsValid = NO;
            }
    
            if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
                NSMutableDictionary *mutableUserInfo = [@{
                                                   NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                                   NSURLErrorFailingURLErrorKey:[response URL],
                                                   AFNetworkingOperationFailingURLResponseErrorKey: response,
                                           } mutableCopy];
    
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }
    
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
    
                responseIsValid = NO;
            }
        }
    
        if (error && !responseIsValid) {
            *error = validationError;
        }
    
        return responseIsValid;
    }
    

    验证response有三个步骤:
    第一步先检查response是否为空和判断response是否是NSHTTPURLResponse类,如果不符合上述条件的话,但是返回的是YES有效的,这个我有点不太理解,后面如果有找到解答的话我会更新上来
    第二步检查response MIME类型是否属于接受的类型,如果没有的话,产生error
    第三步检查接受的状态码是否存在,如果没有的话,产生潜在的error,放在error的userInfo里面,key是NSUnderlyingErrorKey

    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
        [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
    
        return data;
    }
    

    这里只是调用了下validateResponse:data:error:方法,实际上返回的就是传入的data,目的是让子类自己去实现responseObjectForResponse:data:error:
    子类的实现responseObjectForResponse:data:error:也都是先校验response的有效性,然后将data转成相应类型,然后返回出去


    如果有什么意见或者建议,欢迎大家留言,知识是需要交流的,我相信会有更好更简洁的方法来处理

    这个是我的个人微信公众号,会不定期发表一些iOS开发文章以及疑难问题和我在阅读技术和非技术书籍的一些感悟,欢迎大家订阅!

    宫城Dev

    相关文章

      网友评论

      本文标题:iOS AFNetWorking源码详解(二)

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