最近又重新看了一遍AFNetworking,温故知新,因为以前看是看过,也基于AFNetworking封装过网络请求,只是很多点不明白作者为什么那么写,也没深究和记录。这次主要是想在AFNetworking的整体架构和设计方式上分析总结。源码解析推荐这篇文章(AFNetworking到底做了什么?)
准备工作:git上下载zip文件,不用pod主要是想添加自己的注解,可以看到文件只有AFNetworking和UIKit+AFNetworking
而不是平时我们看到pod中
也就是人为分了5个模块:
NSURLSession
Reachability
Serialization
Security
UIKit
说好的不看具体细节,因为前面有大神写好的非常详细文章,还是忍不住画了一下里面结构分类,这里主要是想在心中有个清晰的框架结构。
Demo地址:https://github.com/asd521411/AFNetworkingNote.git
1、网络请求的步骤
- 创建一个请求
NSURLRequest:(包含默认的请求头信息)
NSMutableURLRequest:HTTPMethod、setValue:forHTTPHeaderField:、HTTPBody- 建立连接:
NSURLConnection(NSURLConnection只会开启一条线程,iOS 9已经弃用)
NSURLSession(创建会话)
NSURLSessionDataTask(会话任务)- 创建一个统一网络请求地址:NSURL
- 创建一个请求
NSURLRequest:(包含默认的请求头信息)
NSMutableURLRequest:HTTPMethod、setValue:forHTTPHeaderField:、HTTPBody- 建立连接:
NSURLConnection(NSURLConnection只会开启一条线程,iOS 9已经弃用)
NSURLSession(创建会话)
NSURLSessionDataTask(会话任务)
这是最基本的过程,AFNetworking就是对这些请求的方法做了一些封装(这不废话吗)。
AFHTTPSessionManager
AFURLSessionManager
AFHTTPSessionManager
AFHTTPSessionManager继承于AFURLSessionManager,AFURLSessionManager里面做的事情有:
- 初始化,最终调用的是
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
- 无论GET、POST、HEAD、PUT、PATCH、DELETE最后走的是这个方法
- (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请求
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;
}
和NSURLSessionDataTask请求的的回调
[self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {}];
- 序列化和反序列化拿到(具体看initWithCoder:和encodeWithCoder:里面代码)
self.session.configuration
self.requestSerializer
self.responseSerializer
self.securityPolicy
- 遵守NSCopying协议
- (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
return HTTPClient;
}
AFURLSessionManager里面做的事情
AFURLSessionManager主要是对核心类及其代理的封装
核心类
NSURLSession
NSURLSessionConfiguration
NSURLSessionTask(抽象类)1.NSURLSessionDataTask
2.NSURLSessionDownload代理
NSURLSessionDelegate
NSURLSessionTaskDelegate
NSURLSessionDataDelegate
NSURLSessionDownloadDelegate
NSURLSession官方文档:https://developer.apple.com/documentation/foundation/nsurlsession
具体细节看官方文档
NSURLSession
NSURLSessionDelegate
定义URL会话实例调用其委托来处理会话级事件(如会话生命周期更改)的方法的协议
NSURLSessionTask(抽象类)
NSURLSessionTaskDelegate
定义URL会话实例调用其委托来处理任务级事件
的方法的协议,也就是处理任务时的代理。
NSURLSessionDataTask
一个URL会话任务,它将下载的数据直接返回到内存中的应用程序
NSURLSessionDataDelegate
NSURLSessionDownload
NSURLSessionDownloadDelegate
AFURLSessionManager具体做的事情
- 初始化initWithSessionConfiguration:里面
初始化会话配置、JSON响应、默认安全策略不罗列,看源码
//初始化一个队列,并发数为1,用来处理会话的代理
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//self.mutableTaskDelegatesKeyedByTaskIdentifier
让每一个请求task和自定义的AF代理来建立映射。其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理。
- 异步的获取当前session的所有未完成的task, 防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
}];
- 设置NSMutableURLRequest的属性,并且监听属性的变化,如果改变则把改变的属性放到self.mutableObservedChangedKeyPaths (NSMutableSet类型)里面,在请求的时候从self.mutableObservedChangedKeyPaths里面取出赋值给NSMutableURLRequest属性
定义了一个C语言函数,管理NSMutableURLRequest的属性变化
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;
}
初始化的时候添加观察者
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
是否手动触发KVO通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- 串行执行任务NSURLSessionDataTask
NSURLSession内部去生成task的时候是用多线程并发去执行的。为了适配iOS8的以下,创建session的时候,偶发的情况会出现session的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射delegate的key,所以它必须是唯一的。
dispatch_sync(url_session_manager_creation_queue(), block);
- AF代理和task建立映射,存在事先声明好的字典里。
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
基本流程
- 通常在主线程初始化sessionManager
- 调用get、post等去请求数据,接着会进行request拼接,AF代理的字典映射,progress的KVO添加等等,到NSUrlSession的resume之前这些准备工作,仍旧是在主线程中的。
- 调用NSUrlSession的resume,接着就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
- 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。
- 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
- 如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
2、非AFNetworking网络请求的代码实现
- 创建一个统一网络请求地址:NSURL
- 创建一个请求
NSURLRequest:(包含默认的请求头信息)
NSMutableURLRequest:HTTPMethod、setValue:forHTTPHeaderField:、HTTPBody- 建立连接:
NSURLConnection(NSURLConnection只会开启一条线程,iOS 9已经弃用)
NSURLSession(创建会话)
NSURLSessionDataTask(会话任务)
NSURLRequest:
NSMutableURLRequest:可变的请求对象,比如用POST请的时候要修改请求方法,这是请求就不能用NSURLRequest,要用NSMutableURLRequest。
还是看一下用法:
- (void)connection {
//1、设置请求信息
NSString *urlStr = [NSString stringWithFormat:@""];
//urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];//GET需要转码,POST不需要,因为post参数不是直接拼接在后面
NSURL *url = [NSURL URLWithString:@""];//url有中文需要转码
NSURLRequest *request = [NSURLRequest requestWithURL:url];//默认生成请求头信息
//POST方式
NSMutableURLRequest *mutRequest = [NSMutableURLRequest requestWithURL:url];
mutRequest.HTTPMethod = @"POST";
//设置请求头信息
[mutRequest setValue:@"iOS-Client" forHTTPHeaderField:@"User-Agent"];
//超时请求失败
mutRequest.timeoutInterval = 3;
mutRequest.HTTPBody = [@"username=zhw&pwd=123456&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
NSURLResponse *response = nil;//响应头信息
NSError *error = nil;
//2、请求方式
//2.1同步block方式,此时NSData就是响应体信息
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//解析服务器返回的数据
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//2.2异步请求,block方式
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
}];
//2.3 NSURLConnectionDataDelegate的代理方式
NSURLConnection *connect = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//请求状态
[connect cancel];
}
//NSURLConnectionDataDelegate的代理方法
- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (nullable NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request;
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;
- (nullable NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
使用NSURLSession对象创建Task,然后执行Task,可以开启多条线程。
Task
NSURLSessionTask抽象类,用他的两个子类
1、NSURLSessionDataTaskNSURLSessionUploadTask
2、NSURLSessionDownloadTask
- (void)session {
NSURL *url = [NSURL URLWithString:@""];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//POST方式
NSMutableURLRequest *mutRequest = [NSMutableURLRequest requestWithURL:url];
mutRequest.HTTPMethod = @"POST";
//设置请求头信息
[mutRequest setValue:@"iOS-Client" forHTTPHeaderField:@"User-Agent"];
//超时请求失败
mutRequest.timeoutInterval = 3;
mutRequest.HTTPBody = [@"username=zhw&pwd=123456&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
//创建会话,如果用代理模式返回,就不能用单例模式创建,要自定义创建
NSURLSession *session = [NSURLSession sharedSession];
//block模式
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//注意是子线程
}];
//NSURLSessionDataDelegate模式,要自定义创建NSURLSession,不能用单例模式创建,代理线程队列决定代理方法在哪个线程中执行,传nil在子线程中执行。
NSURLSession *sess = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
NSURLSessionDataTask *ta = [sess dataTaskWithRequest:request];
//默认是挂起状态,恢复执行NSURLSession的代理
[task resume];
[ta resume];
}
//
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//需要通过completionHandler告诉s服务器如何处理返回的数据
completionHandler(NSURLSessionResponseAllow);//告诉服务器结束数据
//打印代理发现也是在子线程中执行的
NSLog(@"%@", [NSThread currentThread]);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;
3、数据解析的方式
- JSON解析
JSON解析三方框架:
JSONKit、SBJson、TouchJSON,性能依次递减
苹果原生自带:NSJSONSerialization(性能最好)
- (void)json {
//JSON-->OC(反序列化)
//+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
//OC-->JSON(序列化)
//+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
NSDictionary *dic = @{@"name":@"zhw", @"age":@"18"};
NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
//并不是所有OC对象都能转成JSON,字符串不行
NSString *str = @"这是一个无聊的字符串";
if ([NSJSONSerialization isValidJSONObject:str]) {//判断对象能否序列化
}
//要把OC对象转成JSON的二进制形式(NSData)再写入文件
[data writeToFile:@"filename" atomically:YES];
//复杂JSON解析三方推荐:MJExtension
}
- XML解析
XML的解析方式有两种:
DOM:一次性将整个XML文件加载进内存,比较适合解析小文件
SAX:从根元素开始,按顺序一个元素一个元素往下解析,比较适合解析大文件
苹果原生:NSXMLParser,SAX解析方式,使用简单
三方框架:libxml2:纯C,默认包含在iOS SDK中,同时支持DOM、SAX解析方式
GDataXML:DOM方式,由Google开发,基于libxml2
大文件建议:NSXMLParser、libxml2
- (void)xml {
NSData *data = [NSData dataWithContentsOfFile:@""];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
parser.delegate = self;
//开始解,阻塞式的
[parser parse];
}
//NSXMLParserDelegate
- (void)parserDidStartDocument:(NSXMLParser *)parser;
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict {
//过滤掉根元素
if ([elementName isEqualToString:@""]) {
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser;