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

版本记录

版本号 时间
V1.0 2018.02.28

前言

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

回顾

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


AFURLSessionManagerTaskDelegate代理为任务设置进度

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

- (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. 上传进度

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

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

(a) 取消

主要就是下面几句代码

[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可能被发送到已被暂停的任务。

(b) 暂停

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

[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是可嵌套的。

(c) 开始

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

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。 您的块不会在任何特定队列上调用。 如果它必须在特定的队列上工作,那么它应该在该队列上安排该工作。

2. 下载进度

首先看一下这部分的代码

[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) 取消

主要对应下面几句代码

[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask cancel];
}];

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

(b) 暂停

主要对应下面几句代码

[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
    __typeof__(weakTask) strongTask = weakTask;
    [strongTask suspend];
}];

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

(c) 开始

主要对应下面这几句代码

if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
    [self.downloadProgress setResumingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask resume];
    }];
}

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

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

主要对应下面这几句代码

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

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

@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);

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

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


AFURLSessionManager为任务添加通知监听

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

[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";

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

后记

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容