iOS开发技术iOS开发代码段iOS网络

iOS AFNetWorking源码详解(二)

2016-03-15  本文已影响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
上一篇下一篇

猜你喜欢

热点阅读