目录
- 前言
- AFURLSessionManager
- 业务流程
- NSURLSession的代理方法
- AFURLSessionManagerTaskDelegate
- 任务进度
- 承接方法
- AFHTTPSessionManager
- 业务流程
- 分片上传
- 一些比较有意思的东西
- 在监听属性的时候、可以用NSStringFromSelector(@selector(xxx))这种方式来自动提示。
- 功能AIP分层
- 如何防止block循环引用
- 把NSURLSession众多代理转化成了block
- 消除编译器clang警告
- 正则的简便写法
- 如何做到对外只读、对内读写
- API注释Demo
- 参考
前言
AFNetworking源码第一篇
主要看了看AFURLSessionManager以及AFHTTPSessionManager相关的API
AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》
AFURLSessionManager
业务流程
先写一个最简单的网络请求
NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager * manager = [[AFURLSessionManager alloc]initWithSessionConfiguration:configuration];
//如果不添加反序列化、有可能会报错说传回来的res是text/html。
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
NSURLSessionDataTask * task = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
}];
[task resume];
进入AFURLSessionManager
内部:
初始化一个新的AFURLSessionManager对象
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
//容错
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
//最大并发为1
self.operationQueue.maxConcurrentOperationCount = 1;
//初始化自己持有的NSURLSession对象
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//设置数据序列化类型
self.responseSerializer = [AFJSONResponseSerializer serializer];
//设置安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
//设置网络监控
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
//mutableTaskDelegatesKeyedByTaskIdentifier 存放着 @{urltask:AFURLSessionManagerTaskDelegate}
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
//对象锁 将来会操作 self.mutableTaskDelegatesKeyedByTaskIdentifier对象
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
//清除磁盘和临时网络缓存。不过按理说刚初始化时应该是空的、可能不理解其中深意。
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
NSLog(@"%@",dataTasks);
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;
}
NSURLRequest生成一个NSURLSessionDataTask对象
- (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;
//内部针对不同iOS版本采用了不同的同步策略保证创建的NSURLSessionDataTask不唯一。具体可以到帖子上去看。
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
为每个NSURLSessionDataTask对象生成对应的delegate对象。
- (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对象出来
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
//与本类关联
delegate.manager = self;
//设置完成回调---由用户传入
delegate.completionHandler = completionHandler;
//添加个描述。具体为self对象的指针str
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//将代理和task关联
//task.taskIdentifier 是由session统一分配的标识符
[self setDelegate:delegate forTask:dataTask];
//设置下载/上传的进度回调---由用户传入
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
关联task与其对应的delegate.将manager从一些无法绕开的数据处理以及传递中解放出来
//关联task与其对应的delegate
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
//两个断言
NSParameterAssert(task);
NSParameterAssert(delegate);
//线程安全
[self.lock lock];
//将delegate 与 task绑定
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
//为AFTaskDelegate设置 task 的进度监听
[delegate setupProgressForTask:task];
//为任务添加监听、包括暂停和开始
//后面还会hook暂停和开始的方法、触发监听
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
NSURLSession的代理方法
NSURLSession的代理被设置为AFURLSessionManager自身。
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
所有的代理、都由AFURLSessionManager承接。
这里manager实现了NSURLSession的所有代理方法。具体作用我感觉应该单开一篇文章来写、毕竟感觉对理解AFN有很大的用处。
《iOS基础深入补完计划--网络模块NSURLSession概述》
大致分为两种
第一种:由manager自行处理、如果用户手动实现则交给用户处理
//重定向
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
//默认为当前链接
NSURLRequest *redirectRequest = request;
if (self.taskWillPerformHTTPRedirection) {
//如果用户实现了重定向、则采用用户的反馈
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
}
if (completionHandler) {
completionHandler(redirectRequest);
}
}
第二种:下放给AFURLSessionManagerTaskDelegate
处理、如果用户手动实现了则(仅当移动文件时
)作废。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
if (delegate) {
//我之前一直很好奇为什么AFURLSessionManagerTaskDelegate与session没有绑定delegate也能调用代理方法
//原来是由manager主动调用的--里面的回调是task级别、也就是创建task时的block
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
//你也可以手动回去完成信息--这个是session级别的。
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
//服务器成功返回数据
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
//下载任务已经完成
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (self.downloadTaskDidFinishDownloading) {
//获取用户手动移动的位置
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (fileURL) {
delegate.downloadFileURL = fileURL;
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
if (error) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
//如果用户自己指定了位置、那就不自动移动文件了
return;
}
}
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
AFURLSessionManagerTaskDelegate
用来承接AFURLSessionManager截获的NSURLSession某些需要特别处理或者说绕不开(进度、任务结束、数据流拼接、下载完成后的文件移动)
的代理。
具体它要管理些什么、很凑巧、基本都展现在属性里了:
@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@property (nonatomic, weak) AFURLSessionManager *manager;//弱持有manager 为了在必要的时候获取manager的队列、序列化、证书配置等信息
@property (nonatomic, strong) NSMutableData *mutableData;//负责下载的数据组合
@property (nonatomic, strong) NSProgress *uploadProgress;//上传进度
@property (nonatomic, strong) NSProgress *downloadProgress;//下载进度
@property (nonatomic, copy) NSURL *downloadFileURL;//文件储存位置、更多是记录作用
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;//文件储存位置(用户创建下载任务时必填)
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;//上传任务进度传递
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;//下载任务进度传递
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;//任务结束时间传递
@end
任务进度
当监听属性发生改变。修改delegate对应progress。
当自己的progress发生改变时、调用对应的block反馈给用户。
#pragma mark - NSProgress Tracking
//设置进度观察
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;
//获取task上传/下载的预期大小
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
//可以取消
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.downloadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
//观察task属性
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
options:NSKeyValueObservingOptionNew
context:NULL];
//观察进度完成
[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))];
[self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
[self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}
//当总大小、进度等有更新时。将与每个taskDelegate对象绑定的Progress传递出去
//以达成用block控制不同任务Progress代理的需求
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
}
}
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
承接方法
任务结束:
//任务结束
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
//忽略系统警告 大概是因为作者下面习惯使用A ?: B这种选择式
#pragma clang diagnostic ignored "-Wgnu"
__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];
//抛弃了self.mutableData的引用、释放出来一些内存。
self.mutableData = nil;
}
if (self.downloadFileURL) {
//保存下载文件储存的位置
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
//保存task获取到的原始数据
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
//请求出错
//保存错误信息
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
// 这里 A ?: B === A ? A : B;
//如果用户没有定制、则使用AF提供的分组和队列
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;
}
//如果用户没有定制、则使用AF提供的分组和队列
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];
});
});
});
}
#pragma clang diagnostic pop
}
得到部分数据时:
这里比我们手动实现NSURLSession
要人性化得多。
因为AFURLSessionManagerTaskDelegate
是跟随任务的、数据容器mutableData
自然也是跟随每个任务。不需要每次开启新任务的时候把原来的容器置空。
//服务器返回了(可能是一部分)数据
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
//组合数据
[self.mutableData appendData:data];
}
下载任务完成时:
//下载任务完成
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
//获取用户自定义存储位置
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
//将位于location原始位置的文件移动到自定义位置
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];
if (fileManagerError) {
//如果失败了发个通知
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
以上三个方法、由manager在同名的代理方法中主动调用。
作用显而易见、两个完成的代理、一个记录receive data的代理。
而manager在其中的作用
- 如果用户实现了对应的block、单纯的将代理中的参数传递给用户。
- 如果属于上述三个方法、交由
AFURLSessionManagerTaskDelegate
处理。
AFHTTPSessionManager
AFURLSessionManager针对HTTP请求特化的扩展。
- 主要体现在Request上
比如请求方式、请求头请求体的配置等等。 - 仅向外提供了几个API
GET、POST、HEAD、PUT以及DELETE - 以及三个属性
/**
请求链接
*/
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
/**
请求器、负责请求头、请求体等配置
*/
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
解码器、负责响应解码。比如json格式等等
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
业务流程
初始化
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// 截取掉最后的'/'
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
//请求路径
self.baseURL = url;
//请求器
self.requestSerializer = [AFHTTPRequestSerializer serializer];
//解码器
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
以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
{
//所有只需要url和参数的请求都要汇聚于此
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
{
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;
//这个就回到AFURLSessionManager的原生方法了
//通过req生成一个数据任务
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;
}
很简单、就是在调用AFURLSessionManager
正常的请求方法dataTaskWithRequest:
之前、根据不同的请求方式生成了不同的Request
、然后帮我们把任务resume
启动而已。
分片上传
一个比较特殊的API
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
//将block的数据以流的形式分片上传
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block 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;
}
//这个就回到AFURLSessionManager的原生方法了
//通过req生成一个分段数据任务
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
里面的主线流程也是一样、唯一不用的是请求Request
的生产方法.
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString]
在用NSURLRequest上传文件时,一般是两种方法:
-
一个是设置body、但是如果文件稍大的话,将会撑爆内存。
-
另外一种则是创建一个临时文件、将数据拼接进去、然后将文件路径设置为bodyStream、这样就可以分片的上传了。
-
而AFN则是更进一步的运用边传边拼的方式上传文件、这无疑是更加高端也是更加繁琐的方法。
具体由于我现在还没有看到具体模块、有兴趣可以先看一下另一篇帖子《AFNetworking分析<二>》 -
使用的话、到时可以举个例子
[manager POST:@"http://localhost/demo/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"头像1.png" withExtension:nil];
NSString *fileName = @"fileName" ;
//AFN会持续的将formData中的数据取出并且转换成不同的AFHTTPBodyPart追加给AFMultipartBodyStream。
//最后将这个AFMultipartBodyStream赋值给NSMutableURLRequest的bodyStream、在网络请求时交由NSURLConnection读取。
[formData appendPartWithFileURL:fileURL name:@"uploadFile" fileName:fileName mimeType:@"image/png" error:NULL];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"OK");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"error");
}];
一些比较有意思的东西
-
在监听属性的时候、可以用
NSStringFromSelector(@selector(xxx))
这种方式来自动提示。
因为属性本身就是与其get方法同名、可以降低出错概率。
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
-
功能AIP分层
AFURLSessionManager
实现了所有的NSURLSessionDelegate
。
但同时又将其中某些需要处理复杂逻辑的代理传递给了AFURLSessionManagerTaskDelegate
。
使得代码更清晰、逻辑更明确。
需要注意的是、AFURLSessionManagerTaskDelegate
完全包裹在了AFURLSessionManager
内部、外界完全感受到他的存在。但是又能做数据处理、这个架构设计真心很赞。
除此之外、AFURLSessionManager
与AFHTTPSessionManager
之间也做了很好的分层。
你可以单独使用AFURLSessionManager
进行网络会话、也可以通过AFHTTPSessionManager
更好的使用AFURLSessionManager
进行HTTP请求。
-
如何防止block循环引用
其实我几年前就听说AFN可以防止循环引用、但是一直没看。
今天找了找发现似乎已经没有了这段代码
所以个人推测现在不会引起循环引用的原因、应该是因为AFN都在作为单例使用、和self并不互相持有。
贴一段以前别人帖子里的代码:
//复写setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//看有没有自定义的完成组,否则用AF的组
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
//看有没有自定义的完成queue,否则用主队列
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
//调用设置的Block,在这个组和队列中
dispatch_group_async(group, queue, ^{
block();
});
//结束时候置nil,防止循环引用
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
[strongSelf setCompletionBlock:nil];
});
}];
}
[self.lock unlock];
}
-
把NSURLSession众多代理转化成了block
这个说实话我并不太暂停...
个人感觉block就是应该控制个数、而NSURLSession的代理加起来起码有二三十个。
如果到了这种数量级的数据传递、真的还是用代理吧、饶了我。
-
消除编译器clang警告
其中Wgnu可以换成其他具体命令
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
-
正则的简便写法
讲道理我还真第一次见
`A ?: B = A ? A : B`
-
如何做到对外只读、对内读写
.h中
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
.m中
@property (readwrite, nonatomic, strong) NSURL *baseURL;
API注释Demo
把注释的源码放在了github上、有兴趣可以自取。
GitHub
参考
【原】AFNetworking源码阅读(四)
AFNetworking分析<二>
iOS基础深入补完计划--网络模块NSURLSession概述
网友评论