Github OC语言排第一, 其凝聚了众多大神的智慧,无论是在技术点上,还是架构设计上、问题处理方式上,都具有很高的学习价值。
大致结构如下:
NSURLSession 请求会话
1. AFURLSessionManager (父类)
核心类就是AFURLSessionManager
了, 管理所有task、证书验证、网络状态、request和response处理。
Task分为4类,分别对应4个Protocol
。根据每个task的属性生成一个AFURLSessionManagerTaskDelegate
代理对象存储在mutableTaskDelegatesKeyedByTaskIdentifierdic
字典中,key为task的ID,即{taskId-delegate}
。并在代理回调中根据taskId
取出delegate
执行相应的代理方法。
基本实现了NSURLSession
的所有代理方法(NSURLSessionDelegate
、NSURLSessionTaskDelegate
、NSURLSessionDataDelegate
、NSURLSessionDownloadDelegate
)。
- 一些代理方法用block包装了出去,下面会介绍。
- 并把最核心的代理回调交给
AFURLSessionManagerTaskDelegate
实现。 - 所有的代理回调都应该在一个串行队列中,这样才能保证代理方法回调的顺序
- 用
NSOperationQueue
: 设置线程最大并发数为 1实现串行,代理回调:异步+串行队列
2. AFHTTPSessionManager (子类)
AFHTTPSessionManager
继成自AFURLSessionManager
,负责创建Get/Head/Post/Put/Patch/Delete
请求,负责管理requestSerializer
(请求序化)和responseSerializer
(响应序列化)
Serialization 序列化
AFURLRequestSerialization (Protocol)
都遵循AFURLRequestSerialization
协议:请求参数序列化
- AFHTTPRequestSerializer:构建普通请求: 格式化请求参数, 生成HTTPHeader; 构建multipart请求, 上传数据时会用到
- AFJSONRequestSerializer:参数格式是 json
- AFPropertyListRequestSerializer:参数格式是苹果的 plist 格式
AFURLResponseSerialization (Protocol)
都遵循AFURLResponseSerialization
协议:验证返回数据,反序列化
- AFHTTPResponseSerializer:普通的 HTTP 请求,默认数据格式是
application/x-www-form-urlencoded
,也就是 key-value 形式的 url 编码字符串 - AFJSONResponseSerializer:对响应进行JSON解析
- AFXMLParserResponseSerializer:对响应进行XML解析
- AFXMLDocumentResponseSerializer (macOS):MIME类型,
application/xml
,text/xml
- AFPropertyListResponseSerializer:MIME类型,
application/x-plist
:苹果的 plist 格式 - AFImageResponseSerializer:支持UIImage or NSImage
- AFCompoundResponseSerializer:组合器, 可以将多个解析器组合起来, 同时支持多种格式的数据解析
(具体说明可以看代码里的)
Additional Functionality
- AFSecurityPolicy
- AFNetworkReachabilityManager
用例分析
方法里处理的东西,可以下载Demo点进去查看,这里考虑到篇幅的原因,就不贴出来了
1. downloadTask
// 1. 创建configuration(配置)
// NSURLSessionConfiguration 有3个工厂方法
// default: 共享 NSHTTPCookieStorage, NSURLCache, NSURLCredentialStorage
// ephemeral: 不会存储 缓存、Cookie、证书, 适用于秘密浏览
// backgroundWithID: 可以在程序 挂起、退出、崩溃 的情况下, 上传和下载任务, ID用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 2. 创建sessionManager
// 2.1.创建网络请求回调queue
// 2.2.创建安全策略
// 2.3.初始化: 代理字典{task-delegate}、锁(访问代理字典的)
// 2.4.遍历session中所有的task: 数据task、上传task、下载task
// 2.4.1 为每个task创建taskDelegate, 并将代理都存入字典中
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 3. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 4. 创建downloadTask
// 4.1 运用NSURLSession, 根据request创建downloadTask (系统方法)
// 4.2 为downloadTask添加taskDelegate
// 4.2.1 根据downloadTask创建taskDelegate
// 4.2.2 为taskDelegate设置: manager、competion回调
// 4.2.3 为taskDelegate设置finish回调
// 4.2.4 将taskDelegate存入字典中 (加锁)
// 接收task的暂停和恢复通知 (通过替换系统的`resume`和`suspend`方法, 添加的notify实现)
// 4.2.5 为taskDelegate设置: progress回调
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request
progress:nil
destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// 返回刚下载的文件路径
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
// 5. 开始执行
[downloadTask resume];
2. uploadTask
// 1. 创建configuration(配置)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 2. 创建sessionManager
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 3. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 4. 获取需要上传文件的URL
NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"photo" ofType:@"PNG"];
NSURL *filePath = [NSURL fileURLWithPath:imageFile];
// 5. 创建uploadTask
// 5.1 运用NSURLSession根据request和fileURL创建uploadTask (系统方法)
// 5.2 为uploadTask添加taskDelegate (详情同 downloadTask 4.2)
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request
fromFile:filePath
progress:nil
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
// 6. 开始执行
[uploadTask resume];
3. multiUploadTask
// 1. 根据method、URL、parameters、可变body block、error, 创建mutableRequest
// 1.1 根据method、URL、parameters、error, 创建mutableRequest
// 1.2 根据mutableRequest创建formData
// 1.3 parameters -> [AFQueryStringPair], 并将其添加在formData后面, 有block则调用并传回formData
// 1.4 设置request
// 1.4.1 设置bodyStream的初始和结尾边界
// 1.4.2 将bodyStream作为请求报文体
// 1.4.3 设置请求头的Content-Type和Content-Length字段
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer]
multipartFormRequestWithMethod:@"POST" // 不支持GET, HEAD类型
URLString:@"http://example.com/upload"
parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// formData支持三种格式的数据: NSData, FileURL, NSInputStream
NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"beautiful" ofType:@"jpg"];
[formData appendPartWithFileURL:[NSURL fileURLWithPath:imageFile]
name:@"file"
fileName:@"filename.jpg"
mimeType:@"image/jpeg"
error:nil];
} error:nil];
// 2. (同downloadTask的1-2)
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
// 3. 运用AFURLSessionManager根据request, 创建streamed uploadTask
// 详情(同upload 5)
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
// 子线程回调, 更新UI需要dispatch到主线程
dispatch_async(dispatch_get_main_queue(), ^{
// refresh UI
});
} completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
// 4. 开始执行
[uploadTask resume];
4. dataTask
// 1. (同downloadTask的1-2)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 2. 根据URL创建request
NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 3. 运用AFURLSessionManager根据request, 创建dataTask
// 3.1 运用NSURLSession根据request, 创建dataTask (系统方法)
// 3.2 为dataTask添加taskDelegate (详情同 downloadTask 4.2)
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
下面是一些巧妙之处:
- 将网络状态由系统的10个简化到4个,详情见
AFNetworkReachabilityStatusForFlags
方法 - 将网络状态改变的通知放到主线程的异步队列中发出,详情见
AFPostReachabilityStatusChange
方法
dispatch_async(dispatch_get_main_queue(), ^{ //mo: 在主线程队列中异步执行
// 发出notification
});
__Require_noErr_Quiet
的使用, 出错跳转到 _out 和 忽略弃用警告宏的使用,详情见AFPublicKeyForCertificate
方法
//mo: __Require_noErr_Quiet: 如果出错, 则跳转到 _out
/* 根据证书和政策创建一个信任管理对象
certificates: 要认证的证书+你认为对证书有用的任何其他证书
policies: 参考评估政策
trust: 返回时, 指向新创建的信任管理对象
*/
__Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out);
//mo: 忽略弃用警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
//mo: 评估指定 证书 和 策略 的信任 (同步的)
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
#pragma clang diagnostic pop
//mo: 在`叶证书`求值后返回其公钥
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
- 因为可变字典的修改不是线程安全的, 所以添加了NSLock,如管理delegate的
mutableTaskDelegatesKeyedByTaskIdentifier
字典的读写:
#pragma mark - mo: 代理字典存取
//mo: 字典的操作不是线程安全的, 所以用`NSLock`加锁
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock]; //mo: 读取加锁
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock]; //mo: 写入加锁
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
-
一些属性的String是用
NSStringFromSelector
生成的 (如: 观察 / 序列化),例如:AFHTTPRequestSerializerObservedKeyPaths
获取需要观察的属性字符串 -
KVO的使用
1.遍历监听自身属性的变化, 将变化的值保存到mutableSet中, 在创建NSMutableURLRequest
时设置
2.当某个属性的getter方法使用其他属性
的值计算返回值时, 重写keyPathsForValuesAffectingValueForKey:
方法, 返回其他属性的集合,详情见AFHTTPRequestSerializer
初始化方法 -
遍历用的
keyEnumerator
/objectEnumerator
/reverseObjectEnumerator
(不可更改的)
//mo: 用`keyEnumerator`遍历keys, 此时不可更改字典 (还有个objectEnumerator可以遍历value)
for (NSString *headerField in headers.keyEnumerator) {
[request setValue:headers[headerField] forHTTPHeaderField:headerField];
}
- GCD的使用: 并行队列+同步,实现读写安全
读: dispatch_sync
写: dispatch_barrier_sync 保证队列之前的任务都执行完毕, 之后的任务得等自己执行完毕
如AFHTTPRequestSerializer
对属性mutableHTTPRequestHeaders
的读写:
// 请求头修改队列:并行
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
#pragma mark - request headers 读写
// mo: 并行同步: 读 / 写(barrier: 保证队列之前的任务都执行完毕, 之后得等自己执行完毕)
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
dispatch_barrier_sync(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
NSString __block *value;
dispatch_sync(self.requestHeaderModificationQueue, ^{
value = [self.mutableHTTPRequestHeaders valueForKey:field];
});
return value;
}
-
初始化方法的处理
NS_UNAVAILABLE
禁用自带初始化函数
NS_DESIGNATED_INITIALIZER
指定初始化函数 -
将代理方法包装成block, 供外部使用
重写了respondsToSelector方法, 将判断方法->判断block,
如AFURLSessionManager
的respondsToSelector:
方法将判断方法是否实现,改为判断相应的block是否为空,然后在代理方法里调用响应的block。 -
NSSecureCoding
而不是NSCoding
解码方法是:decodeObjectOfClass:
而不是decodeObjectForKey:
因为序列化后的数据可能被篡改, 若不指定Class, decode出来的可能不是原来的对象, 有潜在风险 -
帮我们组装好了一些HTTP请求头
如AFHTTPRequestSerializer
的初始化方法: -
Content-Type
:请求参数类型 -
Accept-Language
:根据[NSLocale preferredLanguages]
方法读取本地语言,告诉服务端自己能接受的语言。 -
User-Agent
:app的boundId/ID/版本, 设备型号/系统/尺寸 等 -
Authorization
:提供 Basic Auth 认证接口,帮我们把用户名密码做 base64 编码后放入 HTTP 请求头。
一般我们请求都会按 key=value 的方式带上各种参数,
GET 方法参数直接拼在 URL 后面,POST 方法放在 body 上,
NSURLRequest
没有封装好这个参数的序列化,只能我们自己拼好字符串。
AFHTTPRequestSerializer
提供了接口,让参数可以是NSDictionary
、NSArray
、NSSet
这些类型,再由内部解析成字符串后赋给NSURLRequest
-
_AFURLSessionTaskSwizzling
在+load
将NSURLSessionTask
的-resume
和-suspend
替换成自己的,主要是为了添加自己的通知 -
semaphore_t
/semaphore_signal
/semaphore_wait
的使用
如: 用session的getTask回调获取task时, 运用了semaphore
等待block完成后才return,详情见AFURLSessionManager
的- (NSArray *)tasksForKeyPath:
方法 -
NSProgress的使用, 来监听进度, 控制stack的取消,暂停,恢复
-
Block中使用了StrongSelf调用方法
-
用的 FOUNDATION_EXPORT = extern “C” 指定编译和链接规约, 不影响语义, 只改变编译和链接的方式
-
static修饰const控制常量作用域
-
NSParameterAssert 校验参数完整性
等等,等等。。。
看了一遍,先做一下笔记,以后回顾知新了再更新~
小女子献丑了,文章有哪里不对的,望各位看官指正~
参考文章如下:
AFNetworking(v3.1.0) 源码解析 为何需要使用HTTPs
AFNetworking到底做了什么? 写的非常详细