博客> iOS AFNetWorking源码详解
iOS AFNetWorking源码详解
2017-10-17 14:14 评论:0 阅读:987 wzt

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;

然后调用startMonitoring和stopMonitoring来开始和结束监测,在中间网络状态变化的过程中,你可以通过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 *pinnedCertificates;

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

  • (instancetype)defaultPolicy;
  • (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
  • (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)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 )parameters constructingBodyWithBlock:(nullable void (^)(id 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+XX", 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转成相应类型,然后返回出去

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

收藏
0
sina weixin mail 回到顶部