原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

堯少羽發表於2018-03-16

該文章閱讀的AFNetworking的版本為3.2.0。

這個分類提供了為UIProgressView控制元件繫結task的方法,從而獲取task的上傳下載進度

1.介面檔案

/**
 為指定的上傳任務繫結進度控制元件
 */
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
                                   animated:(BOOL)animated;

/**
 為指定的下載任務繫結進度控制元件
 */
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
                                     animated:(BOOL)animated;
複製程式碼

2.靜態常量

/**
 任務傳送位元組數量上下文標識
 */
static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext;

/**
 任務接受位元組數量上下文標識
 */
static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext;
複製程式碼

3.方法實現

  • 屬性的訪問方法

下面的這四個方法就是通過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);
}
複製程式碼
  • 實現介面方法
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
                                   animated:(BOOL)animated
{
    // 如果任務已經執行完了就不繼續向下執行了
    if (task.state == NSURLSessionTaskStateCompleted) {
        return;
    }
    
    // 通過KVO觀察task的state和countOfBytesSent屬性
    [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
    [task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];

    // 儲存動畫選項
    [self af_setUploadProgressAnimated:animated];
}

- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
                                     animated:(BOOL)animated
{
     // 如果任務已經執行完了就不繼續向下執行了
    if (task.state == NSURLSessionTaskStateCompleted) {
        return;
    }
    
    // 通過KVO觀察task的state和countOfBytesReceived屬性
    [task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
    [task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];

    // 儲存動畫選項
    [self af_setDownloadProgressAnimated:animated];
}
複製程式碼
  • KVO回撥方法
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(__unused NSDictionary *)change
                       context:(void *)context
{
    // 如果是通過這個分類新增的觀察者
    if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
        // 如果是task的countOfBytesSent屬性發生了變化
        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];
                });
            }
        }

        // 如果是task的countOfBytesReceived屬性發生了變化
        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];
                });
            }
        }

        // 如果是task的state屬性發生了變化
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
            // 如果狀態變成了已完成,就移除掉對task屬性的觀察
            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) {}
            }
        }
    }
}
複製程式碼

4.總結

可以看到,繫結進度條控制元件的功能主要是通過KVO觀察task的相關屬性實現的,在KVO的回撥中更新其進度。

原始碼閱讀系列:AFNetworking

原始碼閱讀:AFNetworking(一)——從使用入手

原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization

原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization

原始碼閱讀:AFNetworking(四)——AFSecurityPolicy

原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

原始碼閱讀:AFNetworking(六)——AFURLSessionManager

原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager

原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

原始碼閱讀:AFNetworking(九)——AFImageDownloader

原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

原始碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章