前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听

AFNetworking源码探究(三) —— GET请求实现之任务进度设置和通知监听

原创
作者头像
conanma
修改2021-09-02 14:31:00
4270
修改2021-09-02 14:31:00
举报
文章被收录于专栏:正则

版本记录

版本号

时间

V1.0

2018.02.28

前言

我们做APP发起网络请求,都离不开一个非常有用的框架AFNetworking,可以说这个框架的知名度已经超过了苹果的底层网络请求部分,很多人可能不知道苹果底层是如何发起网络请求的,但是一定知道AFNetworking,接下来几篇我们就一起详细的解析一下这个框架。感兴趣的可以看上面写的几篇。 1. AFNetworking源码探究(一) —— 基本介绍 2. AFNetworking源码探究(二) —— GET请求实现之NSURLSessionDataTask实例化(一)

回顾

上一篇从GET请求入口开始,进行深入分析,包括实例化NSURLSessionDataTask的过程以及为任务添加代理和通知观察。本篇会看一下代理和进度之间的关系以及通知的作用。


AFURLSessionManagerTaskDelegate代理为任务设置进度

主要对应的就是下面这一段代码

代码语言:javascript
复制
- (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];
}

下面我们就一起看一下这个是怎么实现的。

1. 上传进度

关于上传进度,这里涉及到取消、暂停以及重新开始的回调和处理。

代码语言:javascript
复制
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的属性。

代码语言:javascript
复制
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头中获取。这里还算很清晰了,下面简单的介绍和说明。

(a) 取消

主要就是下面几句代码

代码语言:javascript
复制
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask cancel];
}];

首先要设置的就是可取消cancallable这个属性,需要设置为YES。

代码语言:javascript
复制
/* 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中进行取消业务的处理。

代码语言:javascript
复制
[self.uploadProgress setCancellationHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask cancel];
}];

这里就是直接调用任务Task的取消操作[strongTask cancel]

代码语言:javascript
复制
/* -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可能被发送到已被暂停的任务。

(b) 暂停

主要就是对应下面几句代码

代码语言:javascript
复制
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask suspend];
}];

我们看一下[strongTask suspend]

代码语言:javascript
复制
/*
 * 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是可嵌套的。

(c) 开始

主要就是对应下面这段代码

代码语言:javascript
复制
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
    [self.uploadProgress setResumingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask resume];
    }];
}

这里说一下这个resumingHandler,开始进行任务的处理

代码语言:javascript
复制
/* 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。 您的块不会在任何特定队列上调用。 如果它必须在特定的队列上工作,那么它应该在该队列上安排该工作。

2. 下载进度

首先看一下这部分的代码

代码语言:javascript
复制
[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];
    }];
}

和上传一样,包括取消,暂停和开始,下面我们就一起看一下。

(a) 取消

主要对应下面几句代码

代码语言:javascript
复制
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask cancel];
}];

这个不多说了,对比上传的取消。

(b) 暂停

主要对应下面几句代码

代码语言:javascript
复制
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask suspend];
}];

这个不多说了,对比上传的暂停。

(c) 开始

主要对应下面这几句代码

代码语言:javascript
复制
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
    [self.downloadProgress setResumingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask resume];
    }];
}

这个不多说了,对比上传的开始。

3. 给Task和上传下载进度增加KVO观察

主要对应下面这几句代码

代码语言:javascript
复制
[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属性。

代码语言:javascript
复制
/* 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;
代码语言:javascript
复制
/* 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的监听部分。

代码语言:javascript
复制
- (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

代码语言:javascript
复制
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);

else if ([object isEqual:self.downloadProgress]) {
    if (self.downloadProgressBlock) {
        self.downloadProgressBlock(object);
    }
}

如果object是uploadProgress,那么就调用block AFURLSessionTaskProgressBlock

代码语言:javascript
复制
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);

else if ([object isEqual:self.uploadProgress]) {
    if (self.uploadProgressBlock) {
        self.uploadProgressBlock(object);
    }
}

大家可以看到,这上传和下载的block都是同一个类型的block,它们是不同的实例对象而已。


AFURLSessionManager为任务添加通知监听

上一篇讲述过,添加通知监听如下:

代码语言:javascript
复制
[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. 开始

代码语言:javascript
复制
- (NSString *)taskDescriptionForSessionTasks {
    return [NSString stringWithFormat:@"%p", self];
}
代码语言:javascript
复制
- (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,那么就在主线程发送通知。

代码语言:javascript
复制
//通知名字

NSString * const AFNetworkingTaskDidResumeNotification = @"com.alamofire.networking.task.resume";

2. 暂停

代码语言:javascript
复制
- (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不一样而已。

代码语言:javascript
复制
NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networking.task.suspend";

具体,这两个通知有什么用,谁监听做什么,后面会和大家进行说明。

后记

本篇主要讲述的就是lock之内所做的事情,主要包括AFURLSessionManagerTaskDelegate代理为任务设置进度和AFURLSessionManager为任务添加通知监听。后面会继续,喜欢的给个关注~~~。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 版本记录
  • 前言
  • 回顾
  • AFURLSessionManagerTaskDelegate代理为任务设置进度
    • 1. 上传进度
      • 2. 下载进度
        • 3. 给Task和上传下载进度增加KVO观察
        • AFURLSessionManager为任务添加通知监听
          • 1. 开始
            • 2. 暂停
            • 后记
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档