注册
iOS

AFNetworking源码探究 —— UIKit相关之UIProgressView+AFNetworking分类

接口API

下面我们先看一下接口的API

/**
This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task.
*/
@interface UIProgressView (AFNetworking)

///------------------------------------
/// @name Setting Session Task Progress
///------------------------------------

/**
Binds the progress to the upload progress of the specified session task.

@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated;

/**
Binds the progress to the download progress of the specified session task.

@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated;

@end


该类为UIKit框架的UIProgressView类添加方法。 此类别中的方法为将进度绑定到会话任务的上载和下载进度提供了支持。

该接口比较少,其实就是一个上传任务和一个下载任务分别和进度的绑定,可动画。

这里大家还要注意一个关于类的继承的细节。

// 上传
@interface NSURLSessionUploadTask : NSURLSessionDataTask
@interface NSURLSessionDataTask : NSURLSessionTask

// 下载
@interface NSURLSessionDownloadTask : NSURLSessionTask
复制

给大家贴出来就是想让大家注意下这个结构。

runtime获取是否可动画

这里还是用runtime分别绑定下载和上传是否可动画。

- (BOOL)af_uploadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
}

- (void)af_setUploadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (BOOL)af_downloadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}

- (void)af_setDownloadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

这个还算是很好理解的,有了前面的基础,这里就不多说了。

接口的实现

下面我们就看一下接口的实现。

1. 上传任务

static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext;
static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext;
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated
{
if (task.state == NSURLSessionTaskStateCompleted) {
return;
}

[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];

[self af_setUploadProgressAnimated:animated];
}

这里逻辑很清晰,简单的说一下,如果任务是完成状态,那么就直接return,然后给task添加KVO观察,观察属性是state和countOfBytesSent,最后就是设置是否可动画的状态。

2. 下载任务

- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated
{
if (task.state == NSURLSessionTaskStateCompleted) {
return;
}

[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];

[self af_setDownloadProgressAnimated:animated];
}

这里逻辑很清晰,简单的说一下,如果任务是完成状态,那么就直接return,然后给task添加KVO观察,观察属性是state和countOfBytesReceived,最后就是设置是否可动画的状态。


KVO观察实现

下面看一下KVO观察的实现,这里也是这个类的精髓所在。

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context
{
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
if ([object countOfBytesExpectedToSend] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}

if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
if ([object countOfBytesExpectedToReceive] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
});
}
}

if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];

if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}

if (context == AFTaskCountOfBytesReceivedContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
}
}
@catch (NSException * __unused exception) {}
}
}
}
}

这里还是很简单的吧。

  • 如果keyPath是@"countOfBytesSent",那么就获取countOfBytesExpectedToSend,计算进度百分比,在主线程调用[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];得到进度。
  • 如果keyPath是@"countOfBytesReceived",那么就获取countOfBytesExpectedToReceive,计算进度百分比,在主线程调用[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self. af_downloadProgressAnimated];得到进度。
  • 如果keyPath是@"state"并且任务是完成状态NSURLSessionTaskStateCompleted,那么就要移除对这几个keyPath的观察者。

后记

本篇主要分析了UIProgressView+AFNetworking分类,主要实现了上传任务和下载任务与进度之间的绑定。

转载自:https://cloud.tencent.com/developer/article/1872398





0 个评论

要回复文章请先登录注册