- (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];
}
继续上次的内容,代理为任务添加进度:
- (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];
}
接下来看看上面这一段如何实现。
关于上传进度,这里涉及到取消、暂停以及重新开始的回调和处理。
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];
}];
}
首先就是获取上传和下载的总长度,用的就是NSURLSession的属性。
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
/* number of body bytes we expect to send, derived from the Content-Length of the HTTP request */
@property (readonly) int64_t countOfBytesExpectedToSend;
/* number of byte bytes we expect to receive, usually derived from the Content-Length header of an HTTP response. */
@property (readonly) int64_t countOfBytesExpectedToReceive;
这个总的字节数,都可以从HTTP头中获取。这里还算很清晰了,下面简单的介绍和说明。
1. 取消
主要就是下面几句代码
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
首先要设置的就是可取消cancallable
这个属性,需要设置为YES。
/* Whether the work being done can be cancelled or paused, respectively.
By default NSProgresses are cancellable but not pausable. NSProgress is by default
KVO-compliant for these properties, with the notifications always being sent on the thread which updates the property.
These properties are for communicating whether controls for cancelling and pausing should appear in a progress reporting user interface.
NSProgress itself does not do anything with these properties other than help pass their values from progress reporters to progress observers.
It is valid for the values of these properties to change in virtually any way during the lifetime of an NSProgress.
Of course, if an NSProgress is cancellable you should actually implement cancellability by setting a cancellation handler or by making your code poll the result of invoking -isCancelled.
Likewise for pausability.
*/
@property (getter=isCancellable) BOOL cancellable;
所做的工作是否可以分别取消或暂停。 默认情况下,NSProgresses是可取消的,但不可pausable。 对于这些属性,NSProgress默认为符合KVO标准,并且通知始终在更新属性的线程上发送。 这些属性用于传递是否应该在进度报告用户界面中显示取消和暂停的控件。 NSProgress本身不会对这些属性做任何事情,除了帮助将进度记录的值传递给进度观察员。 在NSProgress的生命周期中,这些属性的值实际上以任何方式改变都是有效的。 当然,如果一个NSProgress可以被取消,你应该通过设置一个取消处理程序或者让你的代码轮询调用-isCancelled的结果来实现可取消性。 同样适用于pausability
。
然后就是在cancelHander中进行取消业务的处理。
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
这里就是直接调用任务Task的取消操作[strongTask cancel]
。
/* -cancel returns immediately, but marks a task as being canceled.
* The task will signal -URLSession:task:didCompleteWithError: with an
* error value of { NSURLErrorDomain, NSURLErrorCancelled }. In some
* cases, the task may signal other work before it acknowledges the
* cancelation. -cancel may be sent to a task that has been suspended.
*/
- (void)cancel;
- cancel
立即返回,但将任务标记为被取消。 该任务将发信号-URLSession:task:didCompleteWithError:
错误值为{NSURLErrorDomain,NSURLErrorCancelled}
。 在某些情况下,任务可能在确认取消之前发出其他工作的信号。- cancel
可能被发送到已被暂停的任务。
2. 暂停
主要就是对应下面几句代码
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
我们看一下[strongTask suspend]
/*
* Suspending a task will prevent the NSURLSession from continuing to
* load data. There may still be delegate calls made on behalf of
* this task (for instance, to report data received while suspending)
* but no further transmissions will be made on behalf of the task
* until -resume is sent. The timeout timer associated with the task
* will be disabled while a task is suspended. -suspend and -resume are
* nestable.
*/
- (void)suspend;
- (void)resume;
暂停任务将阻止NSURLSession
继续加载数据。 可能仍然存在代表此任务的代理在调用(例如,报告挂起时收到的数据),但不会有代表任务进行进一步的传输直到发送- resume
。 与任务关联的超时定时器将在任务暂停时被禁用。 - ususpend
和- resume
是可嵌套的。
3. 开始
主要就是对应下面这段代码
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
这里说一下这个resumingHandler
,开始进行任务的处理
/* A block to be invoked when resume is invoked.
The block will be invoked even when the method is invoked on an ancestor of the receiver,
or an instance of NSProgress in another process that resulted from publishing the receiver or an ancestor of the receiver.
Your block won't be invoked on any particular queue.
If it must do work on a specific queue then it should schedule that work on that queue.
*/
@property (nullable, copy) void (^resumingHandler)(void) NS_AVAILABLE(10_11, 9_0);
调用resume
时要调用的块。 即使该方法在接收方的super类上调用,或者由于发布接收方或接收方的super类而导致的另一个进程中的NSProgress
实例,也会调用该block。 您的块不会在任何特定队列上调用。 如果它必须在特定的队列上工作,那么它应该在该队列上安排该工作。
首先看一下这部分的代码
[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];
}];
}
和上传一样,包括取消,暂停和开始,下面我们就一起看一下。
1. 取消
主要对应下面几句代码
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
与上传的取消同理。
2. 暂停
主要就是对应下面几句代码
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
与上传的暂停同理。
3. 开始
主要就是对应下面这段代码
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];
都是self,也就是AFURLSessionManager
,来观察NSURLSessionTask的四个属性countOfBytesReceived、countOfBytesSent、countOfBytesExpectedToSend、countOfBytesExpectedToReceive
以及进度的fractionCompleted
属性。
/* Byte count properties may be zero if no body is expected,
* or NSURLSessionTransferSizeUnknown if it is not possible
* to know how many bytes will be transferred.
*/
/* number of body bytes already received */
@property (readonly) int64_t countOfBytesReceived;
/* number of body bytes already sent */
@property (readonly) int64_t countOfBytesSent;
/* number of body bytes we expect to send, derived from the Content-Length of the HTTP request */
@property (readonly) int64_t countOfBytesExpectedToSend;
/* number of byte bytes we expect to receive, usually derived from the Content-Length header of an HTTP response. */
@property (readonly) int64_t countOfBytesExpectedToReceive;
/* The fraction of the overall work completed by this progress object, including work done by any children it may have.
*/
@property (readonly) double fractionCompleted;
此进度对象完成的全部工作的一小部分,包括可能有的任何子节点所做的工作。
下面我们就一起看一下KVO的监听部分。
- (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);
}
}
}
这里,首先判断object的类型,如果是NSURLSessionTask
或者NSURLSessionDownloadTask
,然后判断的是keyPath,如果是对应属性或者键路径,那么就更新downloadProgress
或者uploadProgress
的几个对应属性的值。
如果object是downloadProgress
,那么就调用block AFURLSessionTaskProgressBlock
。
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
大家可以看到,这上传和下载的block都是同一个类型的block,它们是不同的实例对象而已。
上一篇讲述过,添加通知监听如下:
[self addNotificationObserverForTask: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];
}
1. 开始
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
- (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];
});
}
}
}
一起来看一下这里的逻辑,首先就是取出任务notification.object
,然后判断[task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]
,这里self.taskDescriptionForSessionTasks
的意思就是当前实例化对象的地址。判断如果是YES,那么就在主线程发送通知。
//通知名字
NSString * const AFNetworkingTaskDidResumeNotification = @"com.alamofire.networking.task.resume";
2. 暂停
- (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];
});
}
}
}
暂停的逻辑参考开始,不同的地方就在于发送通知的name不一样而已。
NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networking.task.suspend";
具体,这两个通知有什么用,谁监听做什么,后面会和大家进行说明。