上一篇主要讲了下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;
然后调用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 <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
网友评论