iOS学习----------AFNetworking(5)NSURLSessionTask创建
来源:互联网 发布:tor网络 原理 编辑:程序博客网 时间:2024/05/22 15:18
上两篇中介绍了如何创建不同格式的request、序列化请求参数和处理响应数据、对数据进行格式化(JSON/XML)处理。接下来这一篇进行NSURLSessionTask创建进行详细分析(重点)。
与NSURLSessionTask相关的类有两个,AFURLSessionManager和AFHTTPSessionManager(继承于前者),下面先对AFHTTPSessionManager进行分析。
AFHTTPSessionManager.h文件中主要有以下内容
baseURL
AFURLRequestSerialization * requestSerializer 请求参数序列化
AFURLResponseSerialization * responseSerializer响应数据序列化
初始化操作函数:
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
封装的网络请求函数
GET请求:- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failureGET请求:有下载进度- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;HEAD- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task))success = failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;POST请求(3中形式)- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;POST上传文件- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;POST上传文件(有进度)- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;PATCH- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;PUT- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;DELETE- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
2、AFHTTPSessionManager.m文件
在.m文件中包括一些初始化方法和各个网络请求的实现方法,其中GET、普通的POST、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{ 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; 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;}
POST请求中上传文件的方法调用
该方法先创建multipart格式的post请求,
- (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; 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; } __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;}
3、创建task任务,主要使用的类为AFURLSessionManager
(1)根据request创建dataTask任务,并复制block
- (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 { //创建空的NSURLSessionDataTask __block NSURLSessionDataTask *dataTask = nil; //根据request创建dataTask url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask;}
(2)AFURLSessionManagerTaskDelegate,每一个task任务创建一个AFURLSessionManagerTaskDelegate和task进行关联,通过AFURLSessionManager来管理这两者之间的关系
函数:
创建delegate 并通过函数- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
进行delegate与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; //绑定delegate和dataTask [self setDelegate:delegate forTask:dataTask]; //将传入的blcok复制给delegate delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock;}
绑定的方法
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task{ NSParameterAssert(task); NSParameterAssert(delegate); [self.lock lock]; // session task的taskIdentifier为key,delegate为value,赋值给mutableTaskDelegatesKeyedByTaskIdentifier这个NSMutableDictionary类型的变量self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;使用一下方法设置setupProgressForTask(下载进度)uploadProgress(上传进度),并且将task 和progerss的状态统一 [delegate setupProgressForTask:task]; // [self addNotificationObserverForTask:task]; [self.lock unlock];}
(3)使用一下方法设置setupProgressForTask(下载进度)uploadProgress(上传进度),并且将task 和progerss的状态统一
- (void)setupProgressForTask:(NSURLSessionTask *)task { __weak __typeof__(task) weakTask = task; //progress获取到task任务 期望发送和期望获取的文件的大小 self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive; //将progress和task的状态统一 [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中的countOfBytesReceived为被观察着 接收到数据后这个值会发生变化,observeValueForKeyPath方法监听到值变回会被调用 [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived)) options:NSKeyValueObservingOptionNew context:NULL]; //countOfBytesExpectedToReceive接收到期望文件的大小 [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive)) options:NSKeyValueObservingOptionNew context:NULL]; //countOfBytesSent 发送的文件的大小 [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) options:NSKeyValueObservingOptionNewcontext:NULL]; //countOfBytesExpectedToSend发送的文件的期望大小 [task addObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) options:NSKeyValueObservingOptionNewcontext:NULL]; //注册self.downloadProgress 的fractionCompleted为被观察者 下载的时候接收到部分文件 [self.downloadProgress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; //注册self.downloadProgress 的fractionCompleted为被观察者 上传的时候接收到部分文件 [self.uploadProgress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL];}
当被观察的值发生变化的时候会调用以下方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { /* downloadProgress.completedUnitCount 《== countOfBytesReceived更新 downloadProgress.totalUnitCount 《== countOfBytesExpectedToReceive更新 uploadProgress.completedUnitCount 《== countOfBytesSent更新 uploadProgress.totalUnitCount 《== countOfBytesExpectedToSend更新 调用自定义的downloadProgressBlock 《== downloadProgress.fractionCompleted更新 调用自定义的uploadProgressBlock 《== uploadProgress.fractionCompleted更新 */ if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue]; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { self.uploadProgress.totalUnitCount = [change[@"new"] 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); } }}
(4) //添加两个监听task任务resume和Suspend, 当task任务调用这两个方法的时候会发送通知
- (void)addNotificationObserverForTask:(NSURLSessionTask *)task { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];}
当调用了resume和Suspend方法会接受到通知,并在taskDidResume和taskDidSuspend方法中处理
taskDidResume方法
//当NSURLSessionTask调用resume函数时,会postNotificationName:AFNSURLSessionTaskDidResumeNotification,从而执行taskDidResume:方法:- (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]; }); } }}
4、上传文件POST请求的特殊情况
在AFN中POST请求默认调用的方法为
//StreamedRequest//该方法中创建task的方法为uploadTask = [self.session uploadTaskWithStreamedRequest:request];- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
还提供了两种方法创建task任务
//fromData//该方法中创建task的方法为uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;//fromFile//该方法中创建task的方法为uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
5、下载文件
在AFN中需要用户自己定义request来进行文件的下载
提供了两种方法
//创建request 进行文件下载- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler{ __block NSURLSessionDownloadTask *downloadTask = nil; url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithRequest:request]; }); [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask;}//使用ResumeData下载- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeDataprogress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlockdestination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destinationcompletionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler{ __block NSURLSessionDownloadTask *downloadTask = nil; url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithResumeData:resumeData]; }); [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask;}
总结:上面就完成了 创建sessionTask的任务,并将每个task任务与一个AFURLSessionManagerTaskDelegate对象进行绑定,并将ask.taskIdentifier作为key,delegate作为对象,放在字典self.mutableTaskDelegatesKeyedByTaskIdentifier中。
二、对各个代理方法的处理
AFURLSessionManager遵守的协议NSURLSessionDelegate、NSURLSessionTaskDelegate,后者继承于前者。
类AFURLSessionManagerTaskDelegate遵守的协议NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate。后天两者继承于第一个。
1、NSURLSessionDelegate协议中的方法
//作用:当前这个session已经失效时,该代理方法被调用。//实效方法:finishTasksAndInvalidate-->session首先会先完成最后一个task,然后再调用下面的代理方法invalidateAndCancel-->session会立即调用下面的代理方法URLSession:didBecomeInvalidWithError:代理方法- (void)URLSession:(NSURLSession *)sessiondidBecomeInvalidWithError:(NSError *)error{ //提供了一个外部设置block的set方法 //- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block; if (self.sessionDidBecomeInvalid) { self.sessionDidBecomeInvalid(session, error); } //发出session失效的通知 [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];}/* web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)。 接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书)。最后调用completionHandler回应服务器端的挑战。 1. 当服务器端要求客户端提供证书时或者进行NTLM认证(Windows NT LAN Manager,微软提出的WindowsNT挑战/响应验证机制)时,此方法允许你的app提供正确的挑战证书。 2. 当某个session使用SSL/TLS协议,第一次和服务器端建立连接的时候,服务器会发送给iOS客户端一个证书,此方法允许你的app验证服务期端的证书链(certificate keychain) 如果你没有实现该方法,该session会调用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。*/- (void)URLSession:(NSURLSession *)sessiondidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{ //挑战处理类型为 默认 /* NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理 NSURLSessionAuthChallengeUseCredential:使用指定的证书 NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战 */ NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; // sessionDidReceiveAuthenticationChallenge是自定义方法,用来如何应对服务器端的认证挑战 if (self.sessionDidReceiveAuthenticationChallenge) { disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else { // 此处服务器要求客户端的接收认证挑战方法是NSURLAuthenticationMethodServerTrust // 也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任(即challenge.protectionSpace.serverTrust)产生的挑战证书。 // 而这个证书就需要使用credentialForTrust:来创建一个NSURLCredential对象 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { // 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战 if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { // 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书) credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; // 确定挑战的方式 if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { // 取消挑战 disposition = NSURLSessionAuthChallengeRejectProtectionSpace; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } }// 必须调用此方法,完成认证挑战 if (completionHandler) { completionHandler(disposition, credential); }}- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (self.didFinishEventsForBackgroundURLSession) { // 意味着background session中的消息已经全部发送出去了,返回到主进程执行自定义的函数 dispatch_async(dispatch_get_main_queue(), ^{ self.didFinishEventsForBackgroundURLSession(session); }); }}
2、NSURLSessionTaskDelegate
AFN提供了一系列set方法,用户自定义各个blcok来处理
//客户端告知服务器端需要HTTP重定向。//此方法只会在default session或者ephemeral session中调用,而在background session中,session task会自动重定向。/* 对于NSURLSession对象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三个类工厂方法: +defaultSessionConfiguration 返回一个标准的 configuration,这个配置实际上与 NSURLConnection 的网络堆栈(networking stack)是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享NSURLCredentialStorage。 +ephemeralSessionConfiguration 返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。 +backgroundSessionConfiguration:(NSString *)identifier 的独特之处在于,它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文。 */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskwillPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler{ NSURLRequest *redirectRequest = request; // 自定义如何处理重定向请求,注意会生成一个新的request if (self.taskWillPerformHTTPRedirection) { redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request); } if (completionHandler) { completionHandler(redirectRequest); }}/* 对于session-level的认证挑战,挑战类型有 — NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate, 或NSURLAuthenticationMethodServerTrust — 此时session会调用其代理方法URLSession:didReceiveChallenge:completionHandler:。如果你的app没有提供对应的NSURLSessionDelegate方法,那么NSURLSession对象就会调用URLSession:task:didReceiveChallenge:completionHandler:来处理认证挑战。 对于non-session-level的认证挑战,NSURLSession对象调用URLSession:task:didReceiveChallenge:completionHandler:来处理认证挑战。如果你在app中使用了session代理方法,而且也确实要处理认证挑战这个问题,那么你必须还是在task level来处理这个问题,或者提供一个task-level的handler来显式调用每个session的handler。而对于non-session-level的认证挑战,session的delegate中的URLSession:didReceiveChallenge:completionHandler:方法不会被调用。 */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{ NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.taskDidReceiveAuthenticationChallenge) { disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { disposition = NSURLSessionAuthChallengeUseCredential; credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; } else { disposition = NSURLSessionAuthChallengeRejectProtectionSpace; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); }}/* 当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法。 该代理方法会在下面两种情况被调用: 如果task是由uploadTaskWithStreamedRequest:创建的,那么提供初始的request body stream时候会调用该代理方法。 因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body stream的request,这时候会调用该代理。 */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler{ NSInputStream *inputStream = nil; if (self.taskNeedNewBodyStream) { inputStream = self.taskNeedNewBodyStream(session, task); } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { inputStream = [task.originalRequest.HTTPBodyStream copy]; } if (completionHandler) { completionHandler(inputStream); }}/* 周期性地通知代理发送到服务器端数据的进度。 */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSenttotalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{ // 如果totalUnitCount获取失败,就使用HTTP header中的Content-Length作为totalUnitCount int64_t totalUnitCount = totalBytesExpectedToSend; if(totalUnitCount == NSURLSessionTransferSizeUnknown) { NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"]; if(contentLength) { totalUnitCount = (int64_t) [contentLength longLongValue]; } } // 每次发送数据后的相关自定义处理,比如根据totalBytesSent来进行UI界面的数据上传显示 if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); }}/* 告知该session task已经完成了数据传输任务。 注意这里的error不会报告服务期端的error,他表示的是客户端这边的eroor,比如无法解析hostname或者连不上host主机。 */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error{ AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; // delegate may be nil when completing a task in the background // 如果task是在后台完成的,可能delegate会为nil if (delegate) { [delegate URLSession:session task:task didCompleteWithError:error]; [self removeDelegateForTask:task]; } // 自定义处理方法 if (self.taskDidComplete) { self.taskDidComplete(session, task, error); }}
3、NSURLSessionDataDelegate
/* 告诉代理,该data task获取到了服务器端传回的最初始回复(response)。注意其中的completionHandler这个block,通过传入一个类型为NSURLSessionResponseDisposition的变量来决定该传输任务接下来该做什么: NSURLSessionResponseAllow 该task正常进行 NSURLSessionResponseCancel 该task会被取消 NSURLSessionResponseBecomeDownload 会调用URLSession:dataTask:didBecomeDownloadTask:方法来新建一个download task以代替当前的data task 该方法是可选的,除非你必须支持“multipart/x-mixed-replace”类型的content-type。因为如果你的request中包含了这种类型的content-type,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,session都会调用该函数,你应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果你没有提供该方法的实现,那么session将会继续任务,也就是说会覆盖之前的数据。 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTaskdidReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{ NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; if (self.dataTaskDidReceiveResponse) { disposition = self.dataTaskDidReceiveResponse(session, dataTask, response); } if (completionHandler) { completionHandler(disposition); }}/* 如果data task变化成了下载任务(download task),那么就会调用该代理方法 比如在- URLSession:dataTask:didReceiveResponse:completionHandler:给completionHandler方法传递NSURLSessionResponseBecomeDownload,就会使data task变成download task。而且之前的data task不会再响应代理方法了。 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTaskdidBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask{ AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { // 将delegate关联的data task移除,换成新产生的download task [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); }}* 当接收到部分期望得到的数据(expected data)时,会调用该代理方法。 一个NSData类型的数据通常是由一系列不同的数据整合到一起得到的,不管是不是这样,请使用- enumerateByteRangesUsingBlock:来遍历数据然不是使用bytes方法(因为bytes缺少enumerateByteRangesUsingBlock方法中的range,有了range,enumerateByteRangesUsingBlock就可以对NSData不同的数据块进行遍历,而不像bytes把所有NSData看成一个数据块)。 该代理方法可能会调用多次(比如分片获取数据),你需要自己实现函数将所有数据整合在一起。 */- (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); }}/* 询问data task或上传任务(upload task)是否缓存response。 当task接收到所有期望的数据后,session会调用此代理方法。如果你没有实现该方法,那么就会使用创建session时使用的configuration对象决定缓存策略。这个代理方法最初的目的是为了阻止缓存特定的URLs或者修改NSCacheURLResponse对象相关的userInfo字典。 该方法只会当request决定缓存response时候调用。作为准则,responses只会当以下条件都成立的时候返回缓存: 该request是HTTP或HTTPS URL的请求(或者你自定义的网络协议,并且确保该协议支持缓存) 确保request请求是成功的(返回的status code为200-299) 返回的response是来自服务器端的,而非缓存中本身就有的 提供的NSURLRequest对象的缓存策略要允许进行缓存 服务器返回的response中与缓存相关的header要允许缓存 该response的大小不能比提供的缓存空间大太多(比如你提供了一个磁盘缓存,那么response大小一定不能比磁盘缓存空间还要大5%) */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{ NSCachedURLResponse *cachedResponse = proposedResponse; if (self.dataTaskWillCacheResponse) { cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse); } if (completionHandler) { completionHandler(cachedResponse); }}//当session中所有已经入队的消息被发送出去后,会调用该代理方法。/* 在iOS中,当一个后台传输任务完成或者后台传输时需要证书,而此时你的app正在后台挂起,那么你的app在后台会自动重新启动运行,并且这个app的UIApplicationDelegate会发送一个application:handleEventsForBackgroundURLSession:completionHandler:消息。该消息包含了对应后台的session的identifier,而且这个消息会导致你的app启动。你的app随后应该先存储completion handler,然后再使用相同的identifier创建一个background configuration,并根据这个background configuration创建一个新的session。这个新创建的session会自动与后台任务重新关联在一起。 当你的app获取了一个URLSessionDidFinishEventsForBackgroundURLSession:消息,这就意味着之前这个session中已经入队的所有消息都转发出去了,这时候再调用先前存取的completion handler是安全的,或者因为内部更新而导致调用completion handler也是安全的。 */- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (self.didFinishEventsForBackgroundURLSession) { // 意味着background session中的消息已经全部发送出去了,返回到主进程执行自定义的函数 dispatch_async(dispatch_get_main_queue(), ^{ self.didFinishEventsForBackgroundURLSession(session); }); }}
4、NSURLSessionDownloadDelegate
/* 告诉代理,该下载任务已完成。 */- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location{ //从字典中获得代理 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; if (self.downloadTaskDidFinishDownloading) { NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); // 自定义函数,根据从服务器端获取到的数据临时地址location等参数构建出你想要将临时文件移动的位置 if (fileURL) { // 如果fileURL存在的话,表示用户希望把临时数据存起来 delegate.downloadFileURL = fileURL; NSError *error = nil; // 将位于location位置的文件全部移到fileURL位置 [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]; if (error) { // 如果移动文件失败,就发送AFURLSessionDownloadTaskDidFailToMoveFileNotification [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo]; } return; } } // 这一步比较诡异,感觉有重复的嫌疑。或许是为了兼容以前代码吧 if (delegate) { [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; }}/* 周期性地通知下载进度。 // bytesWritten 表示自上次调用该方法后,接收到的数据字节数 // totalBytesWritten 表示目前已经接收到的数据字节数 // totalBytesExpectedToWrite 表示期望收到的文件总字节数,是由Content-Length header提供。如果没有提供,默认是NSURLSessionTransferSizeUnknown */- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ if (self.downloadTaskDidWriteData) { self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); }}/* 告诉代理,下载任务重新开始下载了。 如果一个resumable(不是很会翻译)下载任务被取消或者失败了,你可以请求一个resumeData对象(比如在userInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取到resumeData)并使用它来提供足够的信息以重新开始下载任务。随后,你可以使用resumeData作为downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的参数。 当你调用这些方法时,你将开始一个新的下载任务。一旦你继续下载任务,session会调用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask参数表示的就是新的下载任务,这也意味着下载重新开始了。 // fileOffset如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么该值为0。 // 否则,该值表示已经存在磁盘上的,不需要重新获取的数据——— 这是断点续传啊! */- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffsetexpectedTotalBytes:(int64_t)expectedTotalBytes{ if (self.downloadTaskDidResume) { self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes); }}
5、调用AFURLSessionManagerTaskDelegate实现的代理方法来处理结果
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wgnu" __strong AFURLSessionManager *manager = self.manager; __block id responseObject = nil; __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; /** * userInfo中的key值例举如下: * AFNetworkingTaskDidCompleteResponseDataKey session 存储task获取到的原始response数据,与序列化后的response有所不同 * AFNetworkingTaskDidCompleteSerializedResponseKey 存储经过序列化(serialized)后的response * AFNetworkingTaskDidCompleteResponseSerializerKey 保存序列化response的序列化器(serializer) * AFNetworkingTaskDidCompleteAssetPathKey 存储下载任务后,数据文件存放在磁盘上的位置 * AFNetworkingTaskDidCompleteErrorKey 错误信息 */ 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 (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data; } // 如果task出错了,处理error信息 // 所以对应的观察者在处理error的时候,比如可以先判断userInfo[AFNetworkingTaskDidCompleteErrorKey]是否有值,有值的话,就说明是要处理error if (error) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = error; // 这里用group方式来运行task完成方法,表示当前所有的task任务完成,才会通知执行其他操作 // 如果没有实现自定义的completionGroup和completionQueue,那么就使用AFNetworking提供的私有的dispatch_group_t和提供的dispatch_get_main_queue内容 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; //json 解析 responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError]; // 注意如果有downloadFileURL,意味着data存放在了磁盘上了,所以此处responseObject保存的是data存放位置,供后面completionHandler处理。没有downloadFileURL,就直接使用内存中的解析后的data数据 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]; }); }); }); }#pragma clang diagnostic pop}#pragma mark - NSURLSessionDataTaskDelegate- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{ [self.mutableData appendData:data];}#pragma mark - NSURLSessionDownloadTaskDelegate- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location{ NSError *fileManagerError = nil; self.downloadFileURL = nil; if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]; if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } }}
- iOS学习----------AFNetworking(5)NSURLSessionTask创建
- iOS学习----------AFNetworking(3)request创建《post请求》
- iOS开发--AFNetworking数据请求
- IOS -AFNetworking 简介及使用
- iOS学习----------AFNetworking(4)responseSerialization
- iOS学习----------AFNetworking(2)request创建和请求参数的序列化
- iOS开发-在Swift里使用AFNetworking方法
- iOS学习----------AFNetworking(1)网络监控
- [AFNetworking]源代码分析--AFURLRequestSerialization.h
- AFNetWorking 3.0请求返回NSData类型数据解决方案--iOS开发
- AFNetWorking 请求返回NSData类型数据解决方案--iOS开发
- iOS之网络请求初解-AFNetworking(包含图片上传)
- iOS--Invocation创建
- AFNetWorking实现SOAP消息发送 ---Web Service
- AFNetworking速成教程(1)--cpf
- xcode文件找不到---“AFNetworking.h”file not found
- xcode文件找不到---“AFNetworking.h”file not found
- xcode文件找不到---“AFNetworking.h”file not found
- Home Screen Quick Actions
- sqlserver 从txt文件入库
- memcached安装
- Spark Streaming和Kafka整合是如何保证数据零丢失
- 设计模式(8)——桥接模式(Bridge Pattern)
- iOS学习----------AFNetworking(5)NSURLSessionTask创建
- logistic回归(机器学习)
- JMS两种模型,布上apache-activemq服务器JMS(四)
- 常用socket函数详解
- 设计模式(9)——装饰者模式(Decorator Pattern)
- building workspace js validation
- VS发布出现 错误 X 未能将文件 复制到 未能找到文件
- @SuppressLint("NewApi")和@TargetApi()的区别
- Java中静态成员