知名的iOS网络框架 AFNetworking 3.0 发布一段时间了,现在来阅读记录一下。(注:我目前阅读的版本是3.1.0)
AFNetworking 3.0 的改动
AFNetworking 3.0 抛弃了基于 NSURLConnection
的API,全力支持 NSURLSession
, 在Xcode7中,苹果明确说明废弃了 NSURLConnection
, 建议全面使用 NSURLSession
:
/*** DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection ***/
所以, AFURLConnectionOperation, AFHTTPRequestOperation , AFHTTPRequestOperationManager 这几个2.0版本中极为关键的几个类已被全部移除。
AFURLSessionManager
AFNetworking 3.0 中关键的几个类:AFURLSessionManager
与 AFHTTPSessionManager
,先来看看 AFURLSessionManager
文件:
- AFURLSessionManager 的初始化,在 initWithSessionConfiguration 方法:
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.....省略......
return self;
}
-
创建NSURLSession时需要设置NSURLSessionConfiguration,NSURLSessionConfiguration 有三种模式:
- 一般模式(default):工作模式类似于原来的NSURLConnection,可以使用缓存的Cache,Cookie,鉴权。
- 及时模式(ephemeral):不保存任何数据到磁盘,不使用缓存的Cache,Cookie,鉴权。
- 后台模式(background):支持在后台完成上传下载 (需要iOS 8以上)
创建NSURLSession时还需要设置 delegate , delegate 用来处理请求中的各种事件,可以设置为nil使用系统提供的delegate,但是要想支持后台传输数据必须提供自定义实现的delegate;另外,NSURLSession对象是强引用了delegate,如果app最终没有调用 invalidateAndCancel 方法 来invalidate 该session的话,则会造成内存泄漏。
创建NSURLSession时可以设置相应的 OperationQueue, 决定请求过程中的一系列事件在哪个 OperationQueue 回调,这里是设置了最大并发量为1的队列,也就相当于串行队列了。(AFNetworing 2.0 版本是设置了一条常驻线程来响应所有网络请求的delegate事件)
2 接着 AFURLSessionManager 当然实现了 NSURLSession Delegate的各个接口,挺容易看的,还是看一看它暴露的创建请求任务的方法吧:
- (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;
}
-
这里使用
url_session_manager_create_task_safely
的方法,是为了解决iOS8以下如果并发创建NSURLSessionTask时会出现的bug(源码里有对应的链接)。这时候获取的 NSURLSessionDataTask 是出于挂起状态的,还不会发起网络请求。NSURLSessionTask 有三个职能不同的子类,- NSURLSessionDataTask: 用于一般的请求资源,以NSData对象的方式返回服务器响应的数据,它不支持backround session;
- NSURLSessionUploadTask: 用于上传,支持backround session;
- NSURLSessionDownloadTask: 用于下载数据到文件中,也支持backround session。
我们再看看里面设置task代理的方法:
- (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 *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
针对于每个data task均有对应的delegate,这个一对一关系是保存在一个字典中,以task的唯一标志作为 key,并且取值赋值删除的时候均要上锁,确保线程安全;
再看一看自定义的 AFURLSessionManagerTaskDelegate 代理对象,它实现了不少功能:
1, 通过KVO的方式监听上传下载的进度并回调出去;
2, 请求接收的数据不断追加到 mutableData :
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
3, 设置当前task完成后的回调:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
它是在NSURLSession的代理方法中被调用的,
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
一开始容易混淆这两个回调,其实是当task完成数据传输后,会回调上面的方法,然后根据task从字典中取出对应的 AFURLSessionManagerTaskDelegate 对象,然后 AFURLSessionManagerTaskDelegate 对象再进行调用完成的callback:
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{.......省略部分.....
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
.........省略............
if (error) {
.......省略........
} 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];
});
});
});
}
}
1, 这里有个技巧,将可变的数据拷贝后,马上将mutableData置为nil来释放回收内存,特别是处理大文件的时候效果就会出来了。具体描述请看这里.
2,它的流程是当请求没有出错时,异步调用 block 处理序列化task的响应对象,然后把数据对象再通过group异步回调出去.
其它:
a. NSStringFromSelector(_cmd)
_cmd表示当前方法的selector,正如self代表了当前方法调用的对象
// AFURLSessionManager中的方法:
- (NSArray *)tasks {
//这里的NSStringFromSelector(_cmd) 与 NSStringFromSelector(@selector(tasks)) 相等
return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}
b. Method Swizzle
在AFURLSessionManager文件中,有一段代码用方法混写的方式修复了iOS7 iOS8 NSURLSessionTask
出现的问题,方法混写的知识已经烂大街了,就不写了,这个问题具体再看源码链接吧。
AFHTTPSessionManager
1, AFHTTPSessionManager
实现类就几百行代码,比较容易看,来看看它提供的GET请求方法吧:
- (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 *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
return dataTask;
}
这里就是根据请求参数序列化创建请求对象设置进度以及请求成功或失败的回调,然后返回dataTask给你并启动,开始真正的网络请求了。
2,按照 AFNetworking 3.0 给出的迁移文档中,可以简单使用 AFHTTPSessionManager 来发起GET请求:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:@"http://example.com/resources.json" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
很熟悉是吧,跟2.0版本一样,获取manager后调用GET方法,但是来看看AFHTTPSessionManager
类的manager 方法:
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
问题来了,如果这么用的话,AFHTTPSessionManager对象调用请求GET方法后,一直没有被释放,因为它一直强引用着session即NSURLSession对象,而session一直被session的delegate强引用着(上面有提到),这样就造成了循环引用导致内存泄漏。这是个坑啊😂。这个问题很早以前就有人在Github上提过了,@mattt当时也回复了这里.
然后我看了AFNetworking 3.0 的示例Demo,发现它是这么用的,创建一个继承AFHTTPSessionManager的类,提供获取单例的方法:
+ (instancetype)sharedClient {
static AFAppDotNetAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]];
_sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
});
return _sharedClient;
}
那么以后再封装AFNetworking 3.0 来用的话,得注意一下这个问题咯。另外swift版本的 Alamofire 的实现是不一样的,有点差别,(呵呵):
public static let sharedInstance: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
return Manager(configuration: configuration)
}()
最后
先写到这里吧,有空再补充。以上均个人见解,欢迎交流。另外,个人简单封装了一下AFNetworking 3.0以便快捷安全使用,地址如下:Githud.