一、基础使用
1.初始化:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
在AFHTTPSessionManager类
中所有的初始化方法最终都会调用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
方法:
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration {
//首先调用了父类AFURLSessionManager的初始化方法
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 如果有效的url后没有正斜线“/”就添加上正斜线“/”,确保baseURL路径的终端斜杠,使NSURL +URLWithString:relativeToURL:工作正常
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
//记录url
self.baseURL = url;
//实例化请求和响应序列化对象
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
在其父类AFURLSessionManager类
中的- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
中做了基本属性的初始化:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
//初始化NSURLSessionConfiguration如果没有传则用默认
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
//初始化一个操作队列并设置最大线程并发数为1
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
//利用已将初始化好的NSURLSessionConfiguration(配置)和NSOperationQueue(队列)初始化NSURLSession并遵守了代理
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//初始化JSON响应序列对象
self.responseSerializer = [AFJSONResponseSerializer serializer];
//初始化安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 初始化网络状态监测对象
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 初始化可变字典保存NSURLSessionTask和其对应的delegate,并以NSURLSessionTask的属性taskIdentifier做key,以NSURLSession的delegate做value
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 初始化self.mutableTaskDelegatesKeyedByTaskIdentifier可变字典的锁,确保字典在多线程访问时的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 异步的获取当前session的所有未完成的task,并进行置空处理,主要是为了从后台切换到前台时重新初始化session
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
2.进行网络请求:
以GET
请求为例:
[manager GET:URLString parameters:parameters progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"下载进度:%@", downloadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"响应对象:%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"错误:%@", error);
}];
进入其调用的方法:
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
// 生成一个NSURLSessionDataTask对象
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
//开始任务
[dataTask resume];
return dataTask;
}
我们发现其原理是调用- (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
这个方法,我们再去看看其他的请求类型,发现GET
和不含body的POST
都是调用这个方法。
- (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
{
// 把所有的参数解析拼接生成一个NSMutableURLRequest对象,如果无法解析则回调返回错误
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) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
// 用生成的NSMutableURLRequest对象生成一个NSURLSessionDataTask对象并回调成功或失败
__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;
}
2.1生成NSURLRequest对象
先看在AFURLRequestSerialization类
中如何利用各种参数生成NSMutableURLRequest对象
:
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 在debug模式下如果缺少参数则会crash
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
// 生成NSMutableURLRequest对象并设置请求方式
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
// 遍历AFHTTPRequestSerializer的各个属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
// 如果发现有正在被观察的属性
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
// 把本类对应属性的值赋给NSMutableURLRequest对应的属性
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 将传入的parameters添加到mutableRequest中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
1.首先,在这个方法中有一个C语言函数static NSArray *AFHTTPRequestSerializerObservedKeyPaths()
这个函数实际上是将AFHTTPRequestSerializer类
中一些属性的名字封装成字符串并以数组形式返回。
2.其次,self.mutableObservedChangedKeyPaths
这个属性在AFURLRequestSerialization
的初始化方法- (instancetype)init
中进行了初始化。
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
// 观察AFHTTPRequestSerializerObservedKeyPaths()函数返回的属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
//注册监听self的keyPath状态
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
并在KVO方法中进行赋值:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
// 如果给当前属性赋的值不为null就添加到self.mutableObservedChangedKeyPaths中,否则从其中移除
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
由此所以我们可以得知self.mutableObservedChangedKeyPaths
中保存的就是我们设置的AFHTTPRequestSerializer
对象的属性的集合。
3.最后,调用方法[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error]
把parameters
编码设置到mutableRequest
中:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 在debug模式下如果缺少NSURLRequest对象则会crash
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 遍历并对request没有的属性进行赋值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 把parameters编码成字符串
NSString *query = nil;
if (parameters) {
// 如果自定义了参数编码方式
if (self.queryStringSerialization) {
NSError *serializationError;
// 用户可通过block自定义参数的编码方式
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
// 使用默认的参数编码方式,也就是下面讲的
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// 判断是否是GET、HEAD、DELETE请求,self.HTTPMethodsEncodingParametersInURI这个属性在AFURLRequestSerialization的初始化方法- (instancetype)init中进行了初始化
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
// 将编码好的参数拼接在url后面
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
// 如果是POST、PUT请求
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 把编码好的参数拼到http的body中
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
大概的看一下AFNetworking
默认的的参数编码方式,AFQueryStringFromParameters()
点进去以后可以看到是三个方法:
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
// 遍历由集合对象处理成AFQueryStringPair元素组成的数组
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
// 把AFQueryStringPair元素的属性拼接成字符串添加到mutablePairs中,如果有value值就拼接成“field=value”的形式,否则为“field”
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 把mutablePairs中的字符串用&链接成一个字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 设置排序描述为按照对象的description属性升序排列
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
// 如果value是NSDictionary
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// 对字典键进行排序,以确保查询字符串中的顺序一致,这在反序列化可能存在歧义的序列(例如字典数组)时非常重要
// 将NSDictionary的key按照首字母升序排列后遍历出nestedKey及其对应的nestedValue,然后递归调用AFQueryStringPairsFromKeyAndValue()方法,如果有key值则传(key[nestedKey], nestedValue),否则传(nestedKey, nestedValue)。
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
// 如果value是NSArray
} else if ([value isKindOfClass:[NSArray class]]) {
// 直接遍历取出nestedValue,然后递归调用AFQueryStringPairsFromKeyAndValue()方法,如果有key值则传递(key[], nestedValue),否则传((null)[], nestedValue)
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
// 如果value是NSSet
} else if ([value isKindOfClass:[NSSet class]]) {
// 将NSSet的值按照首字母升序排列后遍历出值obj,然后递归调用AFQueryStringPairsFromKeyAndValue()方法,如果有key值则传(key, obj),否则传((null), obj)。
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
//实例化AFQueryStringPair对象添加到mutableQueryStringComponents数组中,也就是说AFQueryStringPairsFromKeyAndValue()这个方法执行结束后,返回的是由集合对象转化为AFQueryStringPair对象的元素组成的数组。
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
至此,实例化NSMutableURLRequest对象
的任务就结束了,然后就需要再回到- (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
这个方法中,利用上一步实例化好的NSURLSessionDataTask
对象request
生成网络请求任务NSURLSessionDataTask
。
2.2生成NSURLSessionTask对象
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
发现dataTask并不是直接创建,而是在一个block
中创建,点进url_session_manager_create_task_safely
可以看到这其实是做了一个版本的兼容:
static void url_session_manager_create_task_safely(dispatch_block_t block) {
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// Fix of bug
// Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
// Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
// 苹果在iOS8的时候已经解决了这个bug,这是为了兼容iOS8之前
dispatch_sync(url_session_manager_creation_queue(), block);
} else {
block();
}
}
这里开发者自定义了一个串行队列,并且只生成一次:
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
因此,可以发现如果是在iOS8
之前的版本创建dataTask
是在一个同步串行队列里,通过查找资料,可以知道在iOS8
之前- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
这个方法是异步并发执行的,所以会出现实际回调completionHandlers
并不是当初task
的回调,而是另一个task
的回调。
接下来是对生成的dataTask
添加代理:
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 这个方法里面主要生成了一个AFURLSessionManagerTaskDelegate的对象并和NSURLSessionDataTask对象以及AFURLSessionManager对象相关联
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
接着点[self setDelegate:delegate forTask:dataTask];
进去:
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
// 在debug模式下如果缺少参数则会crash
NSParameterAssert(task);
NSParameterAssert(delegate);
// 加锁保证对字典的操作线程安全
[self.lock lock];
// 将NSURLSessionTask对象的taskIdentifier作为key,将与之对应的AFURLSessionManagerTaskDelegate对象作为value,存放到字典中
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// 为task添加开始与暂停通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
1.在[self addNotificationObserverForTask:task];
方法中添加对task
相关通知的接收:
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
// 给task添加任务开启和暂停的通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}
实现上述为task
注册的两个通知的相关调用函数- (void)taskDidResume:(NSNotification *)notification
和- (void)taskDidSuspend:(NSNotification *)notification
:
- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}
- (void)taskDidSuspend:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
});
}
}
}
为什么不直接发送通知,而是先接收通知然后经过处理后再发送通知呢?
这是因为
AFNetworking
把NSURLSessionTask
的suspend
和resume
方法利用runtime
的Method Swizzling
交换为af_suspend
和af_resume
,并在交换后的方法中发送通知以实现对task
开始与暂停的监听, 但是在一个项目中可能有的网络请求不是当前session
的,所以要通过task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks
进行判断这个网络请求task
是否来自当前session
,其中self.taskDescriptionForSessionTasks
其实就是AFURLSessionManager
对象的指针字符串。
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
到此为止,对task
的处理就结束了。
3.请求回调:
AFURLSessionManager
遵守了<NSURLSessionDelegate> <NSURLSessionTaskDelegate> <NSURLSessionDataDelegate> <NSURLSessionDownloadDelegate>
四个代理并实现了15个代理方法。
这其中AFURLSessionManagerTaskDelegate
只实现了其中6个:
1.先看第一个代理- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
的实现,作为与特定任务相关的最后一条消息发送。Error可以为nil,表示没有发生错误,任务完成:
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
// 因为self.manager属性关键字是weak,所以为了防止被释放就用__strong
__strong AFURLSessionManager *manager = self.manager;
// 用来保存请求返回的数据,为了可以在block中进行修改,用了__block
__block id responseObject = nil;
// 用来保存发送通知时传递的数据,为了可以在block中进行修改,用了__block,并进行赋值
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
// 使用临时变量保存请求到的数据,并把保存数据的属性清空,节约内存
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//我们不再需要引用了,所以把它归零以获得一些内存。
self.mutableData = nil;
}
// 如果设置了下载文件的保存路径,就传递保存路径,否则如果有请求到的数据,就传递请求到的数据
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
// 如果请求出错
if (error) {
// 传递错误信息
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 用户可以自定义调度组和队列并利用dispatch_group_notify实现对回调完成的监控
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
// 回调并发送通知
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
// 如果请求成功
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
// 解析服务器返回的数据
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
// 如果有保存下载文件的路径则返回路径
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
// 传递响应序列化对象
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
// 如果解析出错则传递错误对象
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
// 同样的回调和发送通知
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
2.第二个代理- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
的实现相对简单,当数据可供委托使用时发送,用来保存接收的数据。
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
// 更新下载进度对象的属性
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive; //接收到预计要完成的字节数
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived; //完成的字节数
// 保存传递的数据
[self.mutableData appendData:data];
}
3.第三个代理- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
是在向服务器传递数据时调用,更新上传的进度:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
// 更新上传进度对象的属性
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}
4.第四个代理- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
是在执行下载任务时,定期调用的,定期发送,通知下载进度:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
// 更新下载进度对象的属性
self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}
5.第五个代理- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
在重启下载时调用,当下载已恢复时发送。如果下载失败并出现错误,该错误的-userInfo字典
将包含一个NSURLSessionDownloadTaskResumeData键
,它的值是resume
数据。:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
// 更新下载进度对象的属性
self.downloadProgress.totalUnitCount = expectedTotalBytes;
self.downloadProgress.completedUnitCount = fileOffset;
}
6.第六个代理- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
是下载任务完成后的回调,当下载任务完成下载时发送。委托应将给定位置的文件复制或移动到新位置,因为当委托消息返回时,文件将被删除。URLSession:task:didCompleteWithError:
仍然会被调用。:
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
// 如果用户设置了保存下载文件的路径,就将下载完的文件从临时路径移动过去,移动完成后发送通知
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
4.总结:
到此为止,一个GET
请求就结束了,总结一下AFNetworking
做的事情:
- 首先,
AFHTTPSessionManager
利用参数method
、URLString
和parameters
生成一个NSMutableURLRequest
对象request
。 - 然后,
AFURLSessionManager
利用生成的request
生成一个NSURLSessionDataTask
对象dataTask
。 - 接下来,把
dataTask
和AFURLSessionManagerTaskDelegate
的对象delegate
利用字典一一对应保存,将delegate
的进度属性的取消、暂停和开始与dataTask
相对的方法相关联,并对delegate
和dataTask
的相关属性添加观察者对delegate
的上传下载进度属性赋值。 - 并且,为
dataTask
的开始与停止添加通知监听,并向外部发送通知。 - 接着,调用
dataTask
的resume
方法进行网络请求。 - 最后,通过
AFURLSessionManagerTaskDelegate
的对象delegate
实现的六个代理方法监听进度以及处理请求返回的数据,如果需要解析数据会在异步并发队列中进行,然后在最开始生成的最大并发数为1的NSOperationQueue
的对象operationQueue
串行回调结果。
二、AFURLRequestSerialization
AFHTTPRequestSerializer
这个类是用来构建NSMutableURLRequest
,主要做了请求数据序列化,也就是利用传递来的HTTP请求方法(如:GET)method
、请求URLURLString
和请求参数parameters
来实例化NSMutableURLRequest
类的对象request
。
1.两个全局方法:
1.1 对传入的字符串进行百分号编码
FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
返回一个百分比转义的字符串,紧跟在RFC 3986之后,用于查询字符串键或值。
RFC 3986规定以下字符是“保留”字符。
— 一般分隔符 : “:”, “#”, “[”, “]”, “@”, “?”, “/”
— 子分隔符 : “!”, “$”, “&”, “'”, “(”, “)”, “*”, “+”, “,”, “;”, “=”
@param string 要被百分比转义的字符串。
@return 转义后的字符串。
NSString * AFPercentEscapedStringFromString(NSString *string) {
// 在RFC3986的第3.4节中指出,在对查询字段百分号编码时,保留字符中的“?”和“/”可以不用编码,其他的都要进行编码。
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // 不包括“?”或“/”由于RFC 3986 -章节3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
// 获取URL查询字段允许字符,并从中删除除“?”和“/”之外的保留字符
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
// 每50个字符一组进行百分号编码
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// 每一个中文或者英文在NSString中的length均为1,但是一个Emoji的length的长度为2或者4,这是为了避免截断Emoji表情产生乱码
// 避免中断字符序列,例如 👴🏻👮🏽
//它返回字符串集合中第一个字符的范围,而不是一个字符序列的范围。
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
//返回编码后的字符串
return escaped;
}
1.2 对传入的请求参数进行默认编码
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
一个助手方法,用于生成用于附加到url结尾的编码url查询参数。
@param parameters 要编码的键/值字典。
@return url编码的查询字符串。
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
// 把传入的字典转成元素为AFQueryStringPair对象的数组,然后遍历数组将AFQueryStringPair对象转成经过百分号编码的“key=value”类型NSString对象,最后用“&”拼接成一个字符串
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
以上方法调用了以下方法并把parameters
作为参数传递过去:
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
// 第一个参数key传了nil,第二个参数value传了以上方法传过来的字典
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
下面这个方法就是对字典进行处理,转成元素为AFQueryStringPair
对象的数组:
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 设置排序描述为按照对象的description属性的字母升序排列
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
// 如果参数value传入的是NSDictionary
if ([value isKindOfClass:[NSDictionary class]]) {
// 声明变量保存传入的字典
NSDictionary *dictionary = value;
//对字典键进行排序,以确保查询字符串的顺序一致,这在反序列化可能存在歧义的序列时很重要,例如字典数组
// 将字典的key按照首字母升序排列后进行遍历
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
// 如果遍历出的key所对应的value不为空,就递归调用本方法,如果有key值则传(key[nestedKey], nestedValue),否则传(nestedKey, nestedValue)
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
// 如果参数value传入的是NSArray
} else if ([value isKindOfClass:[NSArray class]]) {
// 声明变量保存传入的数组
NSArray *array = value;
// 遍历数组
for (id nestedValue in array) {
// 递归调用本方法,如果有key值则传递(key[], nestedValue),否则传((null)[], nestedValue)
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
// 如果参数value传入的是NSSet
} else if ([value isKindOfClass:[NSSet class]]) {
// 声明变量保存传入的集合
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
// 递归调用本方法,如果有key值则传(key, obj),否则传((null), obj)
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
// 如果参数value传入的不是集合对象
} else {
// 利用传入的参数key和value实例化AFQueryStringPair对象并添加到mutableQueryStringComponents数组中
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
// 返回由字典对象转化元素为AFQueryStringPair对象组成的数组
return mutableQueryStringComponents;
}
2.两个协议:
2.1. AFURLRequestSerialization协议
这个协议定义了一个方法,用来将参数parameters
拼接到NSURLRequest
对象中。其中类AFHTTPRequestSerializer
、AFJSONRequestSerializer
和AFPropertyListRequestSerializer
都遵守这个协议。
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
返回具有指定参数的请求,并将其编码为原始请求的副本。
@param request 最初的请求。
@param parameters 要编码的参数。
@param error 试图对请求参数进行编码时发生的错误。
@return 一个序列化的请求。
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
2.2. AFMultipartFormData协议
这个协议定义了一系列方法用于在- multipartFormRequestWithMethod:parameters:constructingBodyWithBlock:error:
方法中的代码块中为formData
添加数据。
@protocol AFMultipartFormData
/**
追加HTTP头的Content-Disposition: file;文件名= #{生成文件名};name=#{name}" '和' Content-Type: #{generated mimeType} ',后面跟着编码的文件数据和多部分表单边界。
表单中该数据的文件名和MIME类型将自动生成,分别使用' fileURL '扩展名的最后一个路径组件和与' fileURL '扩展名相关联的系统MIME类型。
@param fileURL 对应于内容将被追加到表单的文件的URL。此参数不能为' nil '。
@param name 与指定数据相关联的名称。此参数不能为' nil '。
@param error 如果发生错误,返回时包含一个描述问题的' NSError '对象。
@return `YES` 如果成功追加文件数据,否则为“NO”。
*/
//将指定路径下数据添加到表单中
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * _Nullable __autoreleasing *)error;
/**
追加HTTP头的Content-Disposition: file;文件名= #{文件名};name=#{name}" '和' Content-Type: #{mimeType} ',后面跟着编码的文件数据和多部分表单边界。
@param fileURL 对应于内容将被追加到表单的文件的URL。此参数不能为' nil '。
@param name 与指定数据相关联的名称。此参数不能为' nil '。
@param fileName 在“Content-Disposition”头中使用的文件名。此参数不能为' nil '。
@param mimeType 文件数据声明的MIME类型。此参数不能为' nil '。
@param error 如果发生错误,返回时包含一个描述问题的' NSError '对象。
@return `YES` 如果成功追加文件数据,否则为“NO”。
*/
//将指定路径下数据添加到表单中,并指定文件类型
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * _Nullable __autoreleasing *)error;
/**
追加HTTP头的Content-Disposition: file;文件名= #{文件名};name=#{name}" '和' Content-Type: #{mimeType} ',后面是来自输入流和多部分表单边界的数据。
@param inputStream 要添加到表单数据的输入流
@param name 与指定输入流相关联的名称。此参数不能为' nil '。
@param fileName 与指定输入流相关联的文件名。此参数不能为' nil '。
@param length 以字节为单位的指定输入流的长度。
@param mimeType 指定数据的MIME类型。(例如,JPEG图像的MIME类型是image/ JPEG。)有关有效MIME类型的列表,请参阅http://www.iana.org/assignments/media-types/。此参数不能为' nil '。
*/
//将指定输入流中的数据添加到表单中
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/**
追加HTTP头的Content-Disposition: file;文件名= #{文件名};name=#{name}" '和' Content-Type: #{mimeType} ',后面跟着编码的文件数据和多部分表单边界。
@param data 要编码并添加到表单数据的数据。
@param name 与指定数据相关联的名称。此参数不能为' nil '。
@param fileName 与指定数据相关联的文件名。此参数不能为' nil '。
@param mimeType 指定数据的MIME类型。(例如,JPEG图像的MIME类型是image/ JPEG。)有关有效MIME类型的列表,请参阅http://www.iana.org/assignments/media-types/。此参数不能为' nil '。
*/
//将指定NSData对象添加到表单中,并指定文件类型
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
/**
追加HTTP头的Content-Disposition: form-data;Name =#{Name}" ',后面跟着编码的数据和多部分表单边界。
@param data 要编码并添加到表单数据的数据。
@param name 与指定数据相关联的名称。此参数不能为' nil '。
*/
//将指定NSData对象添加到表单中
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
/**
追加HTTP头,后面跟着编码的数据和多部分表单边界。
@param headers 要追加到表单数据的HTTP头。
@param body 要编码并添加到表单数据的数据。此参数不能为' nil '。
*/
//将指定的请求头和请求体添加到表单中
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
body:(NSData *)body;
/**
通过限制包大小和为从上传流读取的每个块添加延迟来限制请求带宽。
当通过3G或EDGE连接上传时,请求可能会因为“请求正文流耗尽”而失败。根据推荐值(' kAFUploadStream3GSuggestedPacketSize '和' kAFUploadStream3GSuggestedDelay ')设置最大数据包大小和延迟,可以降低输入流超过分配带宽的风险。不幸的是,没有明确的方法来区分3G, EDGE,或LTE连接的“NSURLConnection”。因此,不建议仅根据网络可达性限制带宽。相反,您应该考虑在失败块中检查“请求正文流耗尽”,然后用节流的带宽重新尝试请求。
@param numberOfBytes 最大数据包大小,以字节数为单位。输入流的默认数据包大小是16kb。
@param delay 每次读取报文的延迟时间。缺省情况下,没有设置延迟时间。
*/
//通过设置请求的带宽和延迟时间来提高在弱网环境下上传数据的成功率
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end
3.一个枚举:
紧接着可以看到一个枚举,定义了请求查询字段的编码方式,不过目前只定义了一种默认方式。
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
AFHTTPRequestQueryStringDefaultStyle = 0,
};
4.三个类:
可以在.h
文件中看到一共有三个类,分别是AFHTTPRequestSerializer
和它的两个子类AFJSONRequestSerializer
、AFPropertyListRequestSerializer
,先来看一下AFHTTPRequestSerializer
这个类:
4.1 AFHTTPRequestSerializer类
先在.h
文件中看一下对外暴漏的接口部分。
4.1.1 接口部分
4.1.1.1 属性
/**
用于序列化参数的字符串编码。默认“NSUTF8StringEncoding”。
*/
//字符串编码方式,默认为NSUTF8StringEncoding
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
创建的请求是否可以使用设备的蜂窝无线(如果存在)。默认“是”。
@see NSMutableURLRequest -setAllowsCellularAccess:
*/
//是否允许使用蜂窝网,默认为是
@property (nonatomic, assign) BOOL allowsCellularAccess;
/**
已创建请求的缓存策略。默认“NSURLRequestUseProtocolCachePolicy”。
@see NSMutableURLRequest -setCachePolicy:
*/
//请求的缓存策略
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
创建的请求是否应该使用默认的cookie处理。默认“是”。
@see NSMutableURLRequest -setHTTPShouldHandleCookies:
*/
//是否将cookies添加到request的header中一同发送给服务器,默认为是
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
创建的请求是否可以在从较早的传输接收响应之前继续传输数据。默认情况下“不”
@see NSMutableURLRequest -setHTTPShouldUsePipelining:
*/
//是否使用管线化,即是否要等到收到前一个请求的响应后才能发送后一个请求,管线化可以一个发送一组请求,不必等待,默认为否
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
创建请求的网络服务类型。默认“NSURLNetworkServiceTypeDefault”。
@see NSMutableURLRequest -setNetworkServiceType:
*/
//网络服务类型,系统会根据设置的类型自动优化
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
创建请求的超时时间,以秒为单位。默认超时时间为60秒。
@see NSMutableURLRequest -setTimeoutInterval:
*/
//超时时长,默认为60秒
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
///---------------------------------------
/// @name 配置HTTP请求报头
///---------------------------------------
/**
应用于序列化请求的默认HTTP报头字段值。默认情况下,这些包括以下内容:
- ' Accept-Language ' 内容为' NSLocale +preferredLanguages '
- ' User-Agent ' 包含各种bundle标识符和OS指定的内容
@discussion 要添加或删除默认的请求头,使用' setValue:forHTTPHeaderField: '。
*/
//请求头信息
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
4.1.1.2 方法
/**
创建并返回带有默认配置的序列化器。
*/
//实例化默认设置对象的方法
+ (instancetype)serializer;
/**
设置HTTP客户端在请求对象中设置的HTTP头的值。如果' nil ',则删除该头文件的现有值。
@param field 要为其设置默认值的HTTP报头
@param value 设置为指定头的默认值,或' nil '
*/
//设置请求头的字段和值,如果值为nil就移除该字段
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
/**
返回请求序列化器中设置的HTTP头的值。
@param field 要检索默认值的HTTP头
@return 设置为指定头的默认值,或' nil '
*/
//获取请求头指定字段的值
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
将HTTP客户端发出的请求对象中设置的“Authorization”HTTP头设置为带有base64编码的用户名和密码的基本身份验证值。这将覆盖此头文件的任何现有值。
@param username HTTP基本认证用户名
@param password HTTP基本认证密码
*/
//利用账号和密码为请求头的“Authorization”字段赋值
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
/**
清除“授权”HTTP报头的任何现有值。
*/
//从请求头中清除“Authorization”字段的值
- (void)clearAuthorizationHeader;
///-------------------------------------------------------
/// @name 配置查询字符串参数序列化
///-------------------------------------------------------
/**
序列化请求将其参数编码为查询字符串的HTTP方法。默认为“GET”,“HEAD”和“DELETE”。
*/
//要把查询字符串编码拼接到URL后面的HTTP请求方法集合,默认为GET、HEAD和DELETE
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
/**
根据预定义的样式之一设置查询字符串序列化的方法。
@param style 序列化的风格。
@see AFHTTPRequestQueryStringSerializationStyle
*/
//设置查询字符串的编码方法,目前AFNetworking只实现了一种,即百分号编码
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
/**
根据指定的块设置查询字符串序列化的自定义方法。
@param block 一个块,它定义了将参数编码为查询字符串的过程。这个块返回查询字符串,并接受三个参数:请求、要编码的参数,以及在试图为给定请求编码参数时发生的错误。
*/
//设置自定义的查询字符串编码方法,只需要在block中实现编码即可
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
///-------------------------------
/// @name 创建请求对象
///-------------------------------
/**
使用指定的HTTP方法和URL字符串创建一个' NSMutableURLRequest '对象。
如果HTTP方法是' GET ', ' HEAD '或' DELETE ',参数将被用于构造一个URL编码的查询字符串,该字符串被附加到请求的URL。否则,参数将根据' parameterEncoding '属性的值进行编码,并设置为请求体。
@param method 请求的HTTP方法,如“GET”、“POST”、“PUT”或“DELETE”。此参数不能为' nil '。
@param URLString 用于创建请求URL的URL字符串。
@param parameters 参数可以设置为“GET”请求的查询字符串,也可以设置为请求HTTP主体。
@param error 构造请求时发生的错误。
@return 一个“NSMutableURLRequest”对象。
*/
//利用传入的HTTP请求方法、请求URL和请求参数三个参数生成NSMutableURLRequest对象。当HTTP请求方法为GET、HEAD或DELETE时,参数会拼接到URL后面,否则,就添加到请求体中
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error;
/**
使用指定的HTTP方法和URLString创建一个' NSMutableURLRequest '对象,并使用指定的参数和多部分表单数据块构造一个' multipart/form-data ' HTTP主体。See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
多部分表单请求会自动流化,直接从磁盘读取文件以及单个HTTP主体中的内存数据。产生的“NSMutableURLRequest”对象有一个“HTTPBodyStream”属性,所以不要在这个请求对象上设置“HTTPBodyStream”或“HTTPBody”,因为它会清除多部分表单正文流。
@param method 请求的HTTP方法。此参数不能为“GET”或“HEAD”或“nil”。
@param URLString 用于创建请求URL的URL字符串。
@param parameters 要在请求HTTP主体中编码和设置的参数。
@param block 一个接受单个参数并向HTTP主体追加数据的块。block参数是一个采用' AFMultipartFormData '协议的对象。
@param error 构造请求时发生的错误。
@return 一个“NSMutableURLRequest”对象
*/
//利用传入的HTTP请求方法、请求URL和请求参数三个参数生成multipart/form-dat请求的NSMutableURLRequest对象。
- (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;
/**
通过从请求中移除' HTTPBodyStream '来创建一个' NSMutableURLRequest ',并异步地将其内容写入指定的文件,完成时调用完成处理程序。
@param request 多部分表单请求。' request '的' HTTPBodyStream '属性不能为' nil '。
@param fileURL 将多部分表单内容写入其中的文件URL。
@param handler 要执行的处理程序块。
@discussion 在' NSURLSessionTask '中有一个bug,导致请求不发送' Content-Length '头时,流内容从HTTP主体,这是明显的问题时,与Amazon S3 webservice交互。解决方法,该方法以一个请求由“multipartFormRequestWithMethod: URLString:参数:constructingBodyWithBlock:错误:”,或任何其他请求“HTTPBodyStream”,将内容写入指定的文件和原始请求的返回一个副本“HTTPBodyStream”属性设置为“零”。从这里,文件可以被传递给' AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler: ',或者将其内容读入' NSData ',该' NSData '被分配给请求的' HTTPBody '属性。
@see https://github.com/AFNetworking/AFNetworking/issues/1398
*/
//移除掉原request中的HTTPBodyStream,并异步写到指定路径下,并返回NSMutableURLRequest对象
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
看完了接口部分,再进入.m
文件中看一下私有实现。
4.1.2 私有实现部分
4.1.2.1 私有全局方法
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
该方法通过一个单例模式获取需要观察的AFHTTPRequestSerializer
对象的属性,并保存在一个数组中返回。
4.1.2.2 私有全局静态变量
static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext;
该变量用于识别观察者的身份。
4.1.2.3 类扩展
@interface AFHTTPRequestSerializer ()
// 用来保存需要观察的用户自定义的AFHTTPRequestSerializer对象的属性
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
// 用来保存请求头信息
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
// 用来保存请求头修饰队列
@property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue;
// 用来保存查询字段编码类型
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
// 用来保存用户自定义的查询字段编码方式代码块
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
@end
4.1.2.4 实现
4.1.2.4.1 生命周期相关方法
+ (instancetype)serializer {
// 就是正常的实例化方法
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
// 初始化字符串编码方式为NSUTF8StringEncoding
self.stringEncoding = NSUTF8StringEncoding;
// 初始化请求头
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
// 初始化请求头修饰队列
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// 获取前五个用户偏好的语言并赋值给请求头Accept-Language字段
// 接收语言HTTP头信息; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
// 获取项目名称(如果没有则获取BundleID)、应用Version版本号(如果没有则获取应用Build版本号)、设备类型、系统版本号和屏幕缩放比并赋值给请求头User-Agent字段
NSString *userAgent = nil;
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
// 如果不能进行无损ASCII编码,即不是只有普通的字符或ASCII码
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
// 如果移除所有非ASCII值范围的所有字符,移除后再次赋值
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
// 初始化需要把查询字符串编码拼接到URL后面的HTTP请求方法集合为GET、HEAD和DELETE方法
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
// 初始化要观察的自定义的AFHTTPRequestSerializer属性集合
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
// 遍历AFHTTPRequestSerializer需要添加观察的属性,添加观察者,并设置上下文为AFHTTPRequestSerializerObserverContext用于标识
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
- (void)dealloc {
// 遍历AFHTTPRequestSerializer需要添加观察的属性,移除观察者
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
}
}
}