【原】AFNetworking源码阅读(三)

【原】AFNetworking源码阅读(三)

本文转载请注明出处 —— polobymulberry-博客园html

1. 前言


上一篇的话,主要是讲了如何经过构建一个request来生成一个data task。可是对于NSURLSession部分却没有说起。主要是精力有限,准备在这一部分把NSURLSession的知识好好梳理一遍。一切先从上一篇中的addDelegateForDataTask:函数提及,而后再介绍AFURLSessionManagerTaskDelegate,最后结合AFURLSessionManager中的NSURLSession梳理一遍(可能会将部份内容放到下一篇)。git

2. 由addDelegateForDataTask引起


注意addDelegateForDataTask:这个函数并非AFURLSessionManagerTaskDelegate的函数,而是AFURLSessionManager的一个函数。这也侧面说明了AFURLSessionManagerTaskDelegate和NSURLSessionTask的关系是由AFURLSessionManager管理的github

该函数除了对于AFURLSessionManagerTaskDelegate类型的成员变量delegate设置以外,最关键的代码就是web

[self setDelegate:delegate forTask:dataTask];

这个setDelegate:forTask:函数字面意思是将一个session task和一个AFURLSessionManagerTaskDelegate类型的delegate变量绑在一块儿,而这个绑在一块儿的工做是由咱们的AFURLSessionManager所作。至于绑定的过程,就是以该session task的taskIdentifier为keydelegate为value,赋值给mutableTaskDelegatesKeyedByTaskIdentifier这个NSMutableDictionary类型的变量。知道了这二者是关联在一块儿的话,立刻就会产生另外的问题 —— 为何要关联以及怎么关联在一块儿?索性咱们好好研究下setDelegate:forTask:这个函数:缓存

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

代码首先是基本的判断,判断session task和delegate是否为空,这里实现方式(NSParameterAssert)每次看到都加深一下印象。接着就是使用NSLock来加锁,这个很简单,和@synchronized做用相似,不过@synchronized多了一个可使用变量做为互斥信号量的功能,这里就不细说了。临界区的代码(lock和unlock之间的代码)也是分为三个部分:安全

1. self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
2. [delegate setupProgressForTask:task];
3. [self addNotificationObserverForTask:task];
  • 第一部分不赘述了。
  • 第二部分我扫了下代码,好像是设置两个NSProgress的变量 - uploadProgress和downloadProgress。
  • 第三部分就是给session task添加了两个KVO事件。

具体细节详述以下(包含第二部分和第三部分详述):服务器

2.1 –[AFURLSessionManager setupProgressForTask:]

上面简单提了下该函数是为了设置uploadProgress和downloadProgress这两个NSProgress变量。这种设置也是很合理的,毕竟session task的任务中须要记录进度的,要不是上传任务,要不就是下载任务。网络

咱们来看看setupProgressForTask:的具体实现:session

- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = 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 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];
}

先是设置两个progress的totalUnitCount:app

self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;

上传的totalUnitCount就对应指望发送(send)的数据大小,下载任务的就对应指望接收(receive)的数据大小。

接着就是设置这两个NSProgress对应的cancelpauseresume这三个状态,正好对应session task的cancel、suspend和resume三个状态,详见上方源码。

最后一部分代码是关键,给session task和两个progress添加KVO。也就是说该AFURLSessionManager的对象须要观察如下属性:

  • NSURLSessionTask的countOfBytesReceived、countOfBytesExpectedToReceive、countOfBytesSent、countOfBytesExpectedToSend属性
  • NSProgress的fractionCompleted属性(任务已经完成的比例,取值为0~1)

看了KVO,立刻跳到observeValueForKeyPath:ofObject:change:context:函数中:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask 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);
        }
    }
}

总结一下,也就是说

  • downloadProgress.completedUnitCount 《==      countOfBytesReceived更新
  • downloadProgress.totalUnitCount          《==      countOfBytesExpectedToReceive更新
  • uploadProgress.completedUnitCount      《==      countOfBytesSent更新
  • uploadProgress.totalUnitCount               《==      countOfBytesExpectedToSend更新
  • 调用自定义的downloadProgressBlock        《==      downloadProgress.fractionCompleted更新
  • 调用自定义的uploadProgressBlock             《==      uploadProgress.fractionCompleted更新

最后两个KVO事件中使用的block其实就是根据NSProgress的状态作用户自定义的行为,好比须要更新UI进度条的状态之类的。

2.2 –[AFURLSessionManager addNotificationObserverForTask:]

- (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];
}

此处若是往深的地方研究,会涉及到不少runtime甚至methodSwizzle的代码,后面会专门开一个章节,研究下这段代码。此处咱们只需知道,当NSURLSessionTask调用resume函数时,会postNotificationName:AFNSURLSessionTaskDidResumeNotification,从而执行taskDidResume:方法:

- (void)taskDidResume:(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:AFNetworkingTaskDidResumeNotification object:task];
            });
        }
    }
}

有了AFNetworkingTaskDidResumeNotification就方便了,以前咱们UIRefreshControl+AFNEtworking中就使用AFNetworkingTaskDidResumeNotification做为NotificationName。同理,参考源码对AFNSURLSessionTaskDidSuspendNotification的处理,这里就不赘述了。

3. 详解AFURLSessionManager的NSURLSession的相关代理


咱们以前在看GET:等这些上层函数时,发现内部实现就是为了生成一个session task。而这个session task与网络具体如何交互,如何处理数据的方法,则是写在NSURLSession的相关代理方法中。虽然GET:这些方法是AFHTTPSessionManager的方法,可是AFURLSessionManager是AFHTTPSessionManager的父类,因此调用的NSURLSession的相关代理的实现实际上是在AFURLSessionManager中实现的,咱们能够看看AFURLSessionManager实现了哪些NSURLSession相关的代理方法:

 ### `NSURLSessionDelegate`

 - `URLSession:didBecomeInvalidWithError:`
 - `URLSession:didReceiveChallenge:completionHandler:`
 - `URLSessionDidFinishEventsForBackgroundURLSession:`

 ### `NSURLSessionTaskDelegate`

 - `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`
 - `URLSession:task:didReceiveChallenge:completionHandler:`
 - `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`
 - `URLSession:task:didCompleteWithError:`

 ### `NSURLSessionDataDelegate`

 - `URLSession:dataTask:didReceiveResponse:completionHandler:`
 - `URLSession:dataTask:didBecomeDownloadTask:`
 - `URLSession:dataTask:didReceiveData:`
 - `URLSession:dataTask:willCacheResponse:completionHandler:`

 ### `NSURLSessionDownloadDelegate`

 - `URLSession:downloadTask:didFinishDownloadingToURL:`
 - `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`
 - `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`

3.1 NSURLSessionDelegate

3.1.1 - URLSession:didBecomeInvalidWithError:

函数声明:

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error

函数做用:

当前这个session已经失效时,该代理方法被调用。

函数讨论:

若是你使用finishTasksAndInvalidate函数使该session失效,那么session首先会先完成最后一个task,而后再调用URLSession:didBecomeInvalidWithError:代理方法,若是你调用invalidateAndCancel方法来使session失效,那么该session会当即调用上面的代理方法。

函数实现:

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    // 自定义的一个block,用来处理session无效的状况。
    // 此处插一句,刚才忽然灵光一现,体验到了block的好处。具体说不清楚,
    // 我只能说好处就是此处并非让用户本身实现didBecomeInvalidWithError:方法,
    // 而是让用户实现sessionDidBecomeInvalid这个block,隐藏细节。
    // 确实很妙,之后要学会使用block
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }
    
    // 当一个session无效时,post名为AFURLSessionDidInvalidateNotification的Notification
    // 不过源代码中没有举例如何使用这个Notification,因此须要用户本身定义,好比结束进度条的显示啊。
    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

3.1.2 - URLSession:didReceiveChallenge:completionHandler:

函数声明:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;

函数做用:

web服务器接收到客户端请求时,有时候须要先验证客户端是否为正经常使用户,再决定是够返回真实数据。这种状况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才须要生成挑战证书)。最后调用completionHandler回应服务器端的挑战。

函数讨论:

该代理方法会在下面两种状况调用:

  1. 1. 当服务器端要求客户端提供证书时或者进行NTLM认证(Windows NT LAN Manager,微软提出的WindowsNT挑战/响应验证机制)时,此方法容许你的app提供正确的挑战证书。
  2. 2. 当某个session使用SSL/TLS协议,第一次和服务器端创建链接的时候,服务器会发送给iOS客户端一个证书,此方法容许你的app验证服务期端的证书链(certificate keychain)

若是你没有实现该方法,该session会调用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 。

函数实现:

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(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);
    }
}

3.1.3 – URLSessionDidFinishEventsForBackgroundURLSession:

函数声明:

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session

函数做用:

当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);
        });
    }
}

3.2 NSURLSessionTaskDelegate

3.2.1 - URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:

函数声明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler

函数做用:

客户端告知服务器端须要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 *)task
willPerformHTTPRedirection:(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);
    }
}

3.2.2 - URLSession:task:didReceiveChallenge:completionHandler:

函数声明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

函数做用:

同NSURLSessionDelegate中的- URLSession:didReceiveChallenge:completionHandler:

函数讨论:

该方法是处理task-level的认证挑战。在NSURLSessionDelegate中提供了一个session-level的认证挑战代理方法。该方法的调用取决于认证挑战的类型:

  • 对于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:方法不会被调用。

函数实现:

参考URLSession:didReceiveChallenge:completionHandler:实现,除了自定义了taskDidReceiveAuthenticationChallenge这个block处理task-level的认证挑战,其余都同样。

3.2.3 - URLSession:task:needNewBodyStream:

函数声明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler

函数做用:

当一个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) {
        // 自定义的获取到新的bodyStream方法
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        // 拷贝一份数据出来到新的bodyStream中(即inputStream)
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}

3.2.4 - URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:

函数声明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

函数做用:

周期性地通知代理发送到服务器端数据的进度。

函数实现:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(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);
    }
}

3.2.5 - URLSession:task:didCompleteWithError:

函数声明:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error

函数做用:

告知该session task已经完成了数据传输任务。

函数讨论:

注意这里的error不会报告服务期端的error,他表示的是客户端这边的eroor,好比没法解析hostname或者连不上host主机。

函数实现:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // 这里第一次展现了AFURLSessionManagerTaskDelegate的做用
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // 若是task是在后台完成的,可能delegate会为nil
    if (delegate) {
        // 调用了同样的代理方法,不过是AFURLSessionManagerTaskDelegate中实现的
        [delegate URLSession:session task:task didCompleteWithError:error];
        // 该task结束了,就移除对应的delegate
        [self removeDelegateForTask:task];
    }
    // 自定义处理方法
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

3.3 NSURLSessionDataDelegate

3.3.1 - URLSession:dataTask:didReceiveResponse:completionHandler:

函数声明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler

函数做用:

告诉代理,该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 *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{  
    // 默认方式为继续执行该task
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
    // 自定义
    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

    if (completionHandler) {
        completionHandler(disposition);
    }
}

3.3.2 - URLSession:dataTask:didBecomeDownloadTask:

函数声明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask

函数做用:

若是data task变化成了下载任务(download task),那么就会调用该代理方法

函数讨论:

好比在- URLSession:dataTask:didReceiveResponse:completionHandler:给completionHandler方法传递NSURLSessionResponseBecomeDownload,就会使data task变成download task。并且以前的data task不会再响应代理方法了。

函数实现:

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(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);
    }
}

3.3.3 - URLSession:dataTask:didReceiveData:

函数声明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

函数做用:

当接收到部分指望获得的数据(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];
    // 调用的是AFURLSessionManagerTaskDelegate的didReceiveData方法
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    // 自定义
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

3.3.4 - URLSession:dataTask:willCacheResponse:completionHandler:

函数声明:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler

函数做用:

询问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;
    // 自定义方法,你能够什么都不作,返回原始的cachedResponse,或者使用修改后的cachedResponse
    // 固然,你也能够返回NULL,这就意味着不须要缓存Response
    if (self.dataTaskWillCacheResponse) {
        cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
    }

    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

3.4 NSURLSessionDownloadDelegate

3.4.1 - URLSession:downloadTask:didFinishDownloadingToURL:(必须实现)

函数声明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

函数做用:

告诉代理,该下载任务已完成。

函数实现:

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        // 自定义函数,根据从服务器端获取到的数据临时地址location等参数构建出你想要将临时文件移动的位置
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        // 若是fileURL存在的话,表示用户但愿把临时数据存起来
        if (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];
    }
}

3.4.2 - URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:

函数声明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite

函数做用:

周期性地通知下载进度。

函数实现:

// bytesWritten 表示自上次调用该方法后,接收到的数据字节数
// totalBytesWritten 表示目前已经接收到的数据字节数
// totalBytesExpectedToWrite 表示指望收到的文件总字节数,是由Content-Length header提供。若是没有提供,默认是NSURLSessionTransferSizeUnknown
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    // 自定义
    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

3.4.3 - URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

函数声明:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes

函数做用:

告诉代理,下载任务从新开始下载了。

函数讨论:

若是一个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)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

4. 还没结束


这一篇因为篇幅缘由,不想继续写下去了。后续内容,包括AFURLSessionManagerTaskDelegate、uploadTask、downloadTask、测试等零散的东西准备放在下一篇进行学习。不过,一切还没结束,AFNetworking源码之路还很长。

5. 参考文章


相关文章
相关标签/搜索