导语
本文目的是实现一个网络请求进度条的动画效果,主要结构分为以下三个部分
JAProgressWKWebView : 使用 WKWebView 的场景
JAProgressUIWebView : 使用 UIWebView 的场景
JAProgressView : 一般情况下使用 NSURLSession 的场景
环境
macOS Sierra 10.12.4 Xcode 8.3.2 iPhone 6S (10.1.1) iPad Mini 2 (8.4)
WKWebView
A WKWebView object displays interactive web content, such as for an in-app browser. You can use the WKWebView class to embed web content in your app. To do so, create a WKWebView object, set it as the view, and send it a request to load web content. Important Starting in iOS 8.0 and OS X 10.10, use WKWebView to add web content to your app. Do not use UIWebView or WebView.
WKWebView 是 Apple 在 iOS 8 时用于替代 UIWebView 的控件
原理
WKWebView 控件提供了如下的属性,用于描述当前页面的加载进度,采用 KVO 的方式,获得进度值并进行相应的操作。
1 2 3 4 5 6 7 8 9 10 11 WKWebView .h @property (nonatomic , readonly ) double estimatedProgress;
实践
添加观察者
1 2 3 4 5 6 7 - (instancetype )initWithFrame:(CGRect )frame configuration:(WKWebViewConfiguration *)configuration { if (self = [super initWithFrame:frame configuration:configuration]) { [self setup]; [self addObserver:self forKeyPath:JAkEstimatedProgress options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil ]; } return self ; }
刷新进度
1 2 3 4 5 - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSKeyValueChangeKey ,id > *)change context:(void *)context { double newKey = [[change objectForKey:NSKeyValueChangeNewKey ] doubleValue]; [self .progressBarlayer flush:newKey]; }
移除观察者
1 2 3 4 5 6 7 - (void )dealloc { @try { [self removeObserver:self forKeyPath:JAkEstimatedProgress]; } @catch (NSException *exception) { } }
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #import "JAWKViewController.h" #import "JAProgressView.h" @interface JAWKViewController () <WKNavigationDelegate >@property (nonatomic ,strong ) JAProgressWKWebView *webView;@end @implementation JAWKViewController - (void )viewDidLoad { [super viewDidLoad]; self .view.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0 ]; self .automaticallyAdjustsScrollViewInsets = false ; WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; JAProgressWKWebView *webView = [[JAProgressWKWebView alloc] initWithFrame:CGRectMake (0 , 64 , [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64 ) configuration:theConfiguration]; webView.backgroundColor = [UIColor groupTableViewBackgroundColor]; webView.opaque = false ; webView.navigationDelegate = self ; [self .view addSubview:_webView = webView]; } - (void )viewWillAppear:(BOOL )animated { [super viewWillAppear:animated]; NSURL *nsurl=[NSURL URLWithString:self .urlString]; NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl]; [_webView loadRequest:nsrequest]; } - (void )webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { NSLog (@"加载完成" ); } - (void )webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { NSLog (@"加载失败" ); [_webView finish]; } - (void )didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
UIWebView
You can use the UIWebView class to embed web content in your app. To do so, create a UIWebView object, attach it to a window, and send it a request to load web content. You can also use this class to move back and forward in the history of webpages, and you can even set some web content properties programmatically. Note In apps that run in iOS 8 and later, use the WKWebView class instead of using UIWebView. Additionally, consider setting the WKPreferences property javaScriptEnabled to NO if you render files that are not supposed to run JavaScript.
原理
先叉开一下话题,看看造好的轮子是如何实现检测网络进度的。下面以 AFNetworking 为例进行说明
在 AFN 中有一个 UIWebView+AFNetworking.h 文件,是为 UIWebView 添加的分类,里面有
1 2 3 4 - (void )loadRequest:(NSURLRequest *)request progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(nullable void (^)(NSError *error))failure;
最终调用的是调用了下面的方法
1 2 3 4 5 6 - (void )loadRequest:(NSURLRequest *)request MIMEType:(nullable NSString *)MIMEType textEncodingName:(nullable NSString *)textEncodingName progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(nullable void (^)(NSError *error))failure;
关注 progress 参数,要求传递的是一个 progress 对象的地址
1 2 3 4 5 6 7 NSURLSessionDataTask *dataTask;... self .af_URLSessionTask = dataTask;if (progress != nil ) { *progress = [self .sessionManager downloadProgressForTask:dataTask]; } [self .af_URLSessionTask resume];
顾名思义 progress 会接收 downloadProgressForTask: 方法的返回值,即本次请求的进度
进到 downloadProgressForTask: 方法中
1 2 3 - (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task { return [[self delegateForTask:task] downloadProgress]; }
查看 downloadProgress 属性在哪个类中
1 2 3 @interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate , NSURLSessionDataDelegate , NSURLSessionDownloadDelegate >... @property (nonatomic , strong ) NSProgress *downloadProgress;
在文件中以 downloadProgress 进行搜索会发现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #pragma mark - NSProgress Tracking - (void )setupProgressForTask:(NSURLSessionTask *)task { __weak __typeof__(task) weakTask = task; ... [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesReceived)) options:NSKeyValueObservingOptionNew context:NULL ]; [task addObserver:self forKeyPath:NSStringFromSelector (@selector (countOfBytesExpectedToReceive)) options:NSKeyValueObservingOptionNew context:NULL ]; ... [self .downloadProgress addObserver:self forKeyPath:NSStringFromSelector (@selector (fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL ]; ...
找到响应的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 - (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]; } } ...
进度的计算是根据 NSURLSession 的两个只读的属性 countOfBytesReceived & countOfBytesExpectedToReceive ,前者是已接收到的数据长度,后者是期望接收到的总长度,来自于 Http 头中的 Content-Length 字段,通过接收这两个值的变化对应去修改 NSProgress 中的 completedUnitCount & totalUnitCount 这两个值
1 2 3 4 5 6 @property int64_t totalUnitCount;@property int64_t completedUnitCount;
fractionCompleted 属性是根据 completedUnitCount / totalUnitCount 得到的。
因此很自然的一种思路是去监听传递的 progress 参数的 fractionCompleted
再此之前先设断点证明下确实走了 setupProgressForTask:
同时因为 AFURLSessionManagerTaskDelegate 并没有暴露给外部,不去修改框架比较好 KVO 是一对多的,也算比较合适。
可添加类似如下的代码对 progress 参数的 fractionCompleted 属性进行观察
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (void )viewWillAppear:(BOOL )animated { [super viewWillAppear:animated]; NSURL *nsurl=[NSURL URLWithString:self .urlString]; NSURLRequest *nsrequest=[NSURLRequest requestWithURL:nsurl]; NSProgress *progress = [[NSProgress alloc] init]; [_webView loadRequest:nsrequest progress:&progress success:^NSString * _Nonnull(NSHTTPURLResponse * _Nonnull response, NSString * _Nonnull HTML) { return @"" ; } failure:^(NSError * _Nonnull error) { }]; [progress addObserver:_webView forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:NULL ]; } #pragma mark - UIWebViewDelegate - (void )webViewDidFinishLoad:(UIWebView *)webView { NSLog (@"加载完成" ); }
但是会发现 _webView 对象中的方法
1 2 3 4 5 6 7 8 9 - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSKeyValueChangeKey ,id > *)change context:(void *)context { dispatch_async (dispatch_get_main_queue(), ^{ double newKey = [[change objectForKey:NSKeyValueChangeNewKey ] doubleValue]; [self .progressBarlayer flush:newKey]; if (1 - newKey < 0.01 ) { [self finish]; } }); }
并没有被执行,跟踪 AFNetowrking
progress 的 fractionCompleted 一直都为 0 因为 countOfBytesExpectedToReceive 的总长度是 -1 (0xffffffffffffffff ) 这一点可以在 issues - Progress and KVO never called 得到佐证,主要是因为请求头的 Content-Length 字段未设置。
访问 https://www.baidu.com
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000000b6d 2925 (lldb) po dataTask.response <NSHTTPURLResponse : 0x174030160 > { URL: https: Connection = "keep-alive" ; "Content-Encoding" = gzip; "Content-Type" = "text/html" ; Date = "Sat, 13 May 2017 09:14:00 GMT" ; P3p = "CP=\" OTI DSP COR IVA OUR IND COM \"" ; Server = "bfe/1.0.8.18" ; "Set-Cookie" = "BAIDUID=623A610A25264EC09CC77FECD44A7CE4:FG=1; max-age=31536000; expires=Sun, 13-May-18 09:14:00 GMT; domain=.baidu.com; path=/; version=1, H_WISE_SIDS=102431; path=/; domain=.baidu.com, BDSVRTM=9; path=/, __bsi=13135479753770821857_00_33_N_N_11_0303_C02F_N_N_Y_0; expires=Sat, 13-May-17 09:14:05 GMT; domain=www.baidu.com; path=/" ; Traceid = 149466684009534243944441198276626132688 ; "Transfer-Encoding" = Identity; Vary = "Accept-Encoding" ; } } (lldb) po dataTask.countOfBytesExpectedToReceive 2925 (lldb)
访问 https://www.bing.com
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0xffffffffffffffff -1 (lldb) po dataTask.response <NSHTTPURLResponse : 0x17022c6c0 > { URL: https: "Cache-Control" = "private, max-age=0" ; "Content-Encoding" = gzip; "Content-Length" = 32317 ; "Content-Type" = "text/html; charset=utf-8" ; Date = "Sat, 13 May 2017 09:16:11 GMT" ; P3P = "CP=\"NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND\"" ; Server = "Microsoft-IIS/10.0" ; "Set-Cookie" = "_SS=SID=3C3C051FE4516B5A18710F9EE5F06A6F; domain=.bing.com; path=/, _EDGE_S=SID=3C3C051FE4516B5A18710F9EE5F06A6F; path=/; httponly; domain=bing.com" ; Vary = "Accept-Encoding" ; "X-MSEdge-Ref" = "Ref A: 0505C24953B941CA96CF74FAE726B298 Ref B: BJ1EDGE0322 Ref C: Sat May 13 02:16:11 2017 PST" ; } } (lldb) po dataTask.countOfBytesExpectedToReceive -1 (lldb)
访问 https://www.google.com
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0xffffffffffffffff -1 (lldb) po dataTask.response <NSHTTPURLResponse : 0x170033180 > { URL: https: "Alt-Svc" = "quic=\":443\"; ma=2592000; v=\"37,36,35\"" ; "Cache-Control" = private; "Content-Encoding" = gzip; "Content-Type" = "text/html; charset=Big5" ; Date = "Sat, 13 May 2017 09:24:46 GMT" ; Expires = "Sat, 13 May 2017 09:24:46 GMT" ; P3P = "CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"" ; Server = gws; "Set-Cookie" = "NID=103=adCbLI6_cKax8zD5WTTC6Xq-Vviron4fGJDZeVf1OEiQhvSD9L3Q53n8HrmZS8--BUJIIAbR_cb5UwweP0nvyOKD0J4tbOLf6tr-p_Vva5mnAiYyWKKzsOhqtc9SjBhW; expires=Sun, 12-Nov-2017 09:24:46 GMT; path=/; domain=.google.com; HttpOnly" ; "Transfer-Encoding" = Identity; "X-Frame-Options" = SAMEORIGIN; "X-XSS-Protection" = "1; mode=block" ; } } (lldb) po dataTask.countOfBytesExpectedToReceive -1
第二次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 0x0000000000002ae7 - Hook 1 (expr -- @import UIKit ) - Hook 2 ( target stop-hook disable) 10983 (lldb) po dataTask.response <NSHTTPURLResponse : 0x17003cd60 > { URL: https: "Alt-Svc" = "quic=\":443\"; ma=2592000; v=\"37,36,35\"" ; "Cache-Control" = private; "Content-Encoding" = gzip; "Content-Type" = "text/html; charset=Big5" ; Date = "Sat, 13 May 2017 10:09:01 GMT" ; Expires = "Sat, 13 May 2017 10:09:01 GMT" ; P3P = "CP=\"This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info.\"" ; Server = gws; "Set-Cookie" = "NID=103=f7RScaRo91vaPuGoStsjYJrDLUSANq6fwzBypLCvMeUasdsx443kwCfnzioj167ke0Buh6c6qVsA_xiT8olCjCvOtOd4P9drblpB5PkDMGiWg0J6qJnBuhoeFQIREDVT; expires=Sun, 12-Nov-2017 10:09:01 GMT; path=/; domain=.google.com; HttpOnly" ; "Transfer-Encoding" = Identity; "X-Frame-Options" = SAMEORIGIN; "X-XSS-Protection" = "1; mode=block" ; } } (lldb) po dataTask.countOfBytesExpectedToReceive 10983
搜索 Content-Length 字段的相关内容
rfc2616.html#header.content-length
rfc2616.html#message.length
在 HTTP 1.1 中应用可以通过 Content-Lenght 字段确定消息的长度,
Content-Length 失效的几种情况
状态码为 1xx ,204 和 304
设置了 Transfer-Encoding 且该值不等于 identity
同时存在 Transfer-Encoding 和 Content-Length
响应的数据类型为 multipart/byteranges 且没有指定 transfer-length
服务器关闭连接
在 这里
1、在Http 1.0及之前版本中,content-length字段可有可无。 2、在http1.1及之后版本。如果是keep alive,则content-length和chunk必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无。
Transfer-Encoding
用来指定数据传输的格式,有如下的格式
Transfer-Encoding: chunked Transfer-Encoding: compress Transfer-Encoding: deflate Transfer-Encoding: gzip Transfer-Encoding: identity
chunked 的格式详情可以参考 Wiki-分块传输编码
如果一个HTTP消息(请求消息或应答消息)的Transfer-Encoding消息头的值为chunked,那么,消息体由数量未定的块组成,并以最后一个大小为0的块为结束。 每一个非空的块都以该块包含数据的字节数(字节数以十六进制表示)开始,跟随一个CRLF (回车及换行),然后是数据本身,最后块CRLF结束。在一些实现中,块大小和CRLF之间填充有白空格(0x20)。 最后一块是单行,由块大小(0),一些可选的填充白空格,以及CRLF。最后一块不再包含任何数据,但是可以发送可选的尾部,包括消息头字段。 消息最后以CRLF结尾。
但是到这里依然没完全解决我的困惑,在 iOS 中如何比较获得网络请求的总长度?
寻找头文件后发现,在 NSURLResponse 对象中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @property (readonly ) long long expectedContentLength;
我的方案
经过测试,在 NSURLSessionDataDelegate 的代理方法URLSession:dataTask:didReceiveData: , data 即为每次获取的数据,而 dataTask 中的 response 属性的 expectedContentLength 有时会在数据并没有完全接收到时就可以获得长度,因此对 UIWebView 用这个值来获取网络请求进度,采用的主要思路是
在未获得 expectedContentLength 长度时,用数组保存每次 data 的值
获取到 expectedContentLength , 根据数组中的值算出进度,在本地实现动画。
网络请求暂时决定还是依赖 AFNetworking , 添加了一个分类,在运行时替换 URLSession:dataTask:didReceiveData: 方法来实现
实践
分类 AFHTTPSessionManager+JACoder.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #import "AFHTTPSessionManager+JACoder.h" #import <objc/message.h> NSString *kAFJAReceiveDataNotification = @"kAFNReceiveDataNotification" ;NSString *kAFJAReceiveResponseNotification = @"kAFJAReceiveResponseNotification" ;@implementation AFHTTPSessionManager (JACoder )+ (void )load { Method originalMethod = class_getInstanceMethod(self , @selector (URLSession:dataTask:didReceiveData:)); Method swizzledMethod = class_getInstanceMethod(self , @selector (ja_URLSession:dataTask:didReceiveData:)); BOOL didAddMethod = class_addMethod(self , @selector (URLSession:dataTask:didReceiveData:), method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(self , @selector (ja_URLSession:dataTask:didReceiveData:), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } - (void )ja_URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self ja_URLSession:session dataTask:dataTask didReceiveData:data]; if (dataTask.response.expectedContentLength != -1 && self .isLock == false ) { [[NSNotificationCenter defaultCenter] postNotificationName:kAFJAReceiveDataNotification object:data]; [[NSNotificationCenter defaultCenter] postNotificationName:kAFJAReceiveResponseNotification object:dataTask]; self .lock = true ; }else { [[NSNotificationCenter defaultCenter] postNotificationName:kAFJAReceiveDataNotification object:data]; } } - (BOOL )isLock { return (BOOL )[objc_getAssociatedObject(self , @selector (isLock)) doubleValue]; } - (void )setLock:(BOOL )lock { objc_setAssociatedObject(self , @selector (setLock:), @(lock), OBJC_ASSOCIATION_ASSIGN); } @end
接收到已获取总长度的通知后的相应处理,为了和 WKWebView 统一,在继承 UIWebView 的子类中添加了 estimatedProgress 属性,同样用 KVO 去处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 - (void )receiveWithNotification:(NSNotification *)noti { if ([noti.object isKindOfClass:[NSURLSessionDataTask class ]]) { NSURLSessionDataTask *dataTask = (NSURLSessionDataTask *)noti.object; self .expectedContentLength = dataTask.response.expectedContentLength; if (self .estimatedProgress == 0 ) { if (self .records.count == 0 ) { self .estimatedProgress = 1.0 ; }else { for (NSData *record in self .records) { self .estimatedProgress += (CGFloat )record.length / self .expectedContentLength; } self .records = [NSArray array]; } }else { for (NSData *record in self .records) { self .estimatedProgress += (CGFloat )record.length / self .expectedContentLength; } } } if ([noti.object isKindOfClass:[NSData class ]]) { NSData *data = (NSData *)noti.object; if (self .expectedContentLength != -1 && self .expectedContentLength != 0 ) { for (NSData *record in self .records) { self .estimatedProgress += (CGFloat )record.length / self .expectedContentLength; } self .estimatedProgress += (CGFloat )data.length / self .expectedContentLength; self .records = [NSArray array]; }else { NSMutableArray *recordsM = [NSMutableArray arrayWithArray:self .records]; [recordsM addObject:data]; self .records = [recordsM copy ]; } } }
estimatedProgress 属性变化的处理
1 2 3 4 5 6 7 8 9 - (void )observeValueForKeyPath:(NSString *)keyPath ofObject:(id )object change:(NSDictionary <NSKeyValueChangeKey ,id > *)change context:(void *)context { dispatch_async (dispatch_get_main_queue(), ^{ double newKey = [[change objectForKey:NSKeyValueChangeNewKey ] doubleValue]; [self .progressBarlayer flush:newKey]; if (1 - newKey < 0.01 ) { [self finish]; } }); }
UIView
对于一般的 App 请求,大多都是返回 JSON 数据的接口,也可以做一个进度条的效果。
原理
一事不烦二主,也用 AFNetworking 做请求。上面已经分析了 AFNetworking 如何检测请求进度的, 大多数情况下的请求进度肯定是没问题的。
实践
用 AFNetworking 发起请求,根据回调返回的 NSProgress 值实现刷新操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #import "JAUIViewController.h" #import <AFNetworking.h> #import "JAProgressView.h" @interface JAUIViewController ()@property (nonatomic ,strong ) JAProgressView *progressView;@end @implementation JAUIViewController - (void )viewDidLoad { [super viewDidLoad]; self .view.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0 ]; _progressView = [[JAProgressView alloc] init]; [self .navigationController.navigationBar addSubview:_progressView]; } - (void )viewWillAppear:(BOOL )animated { [super viewWillAppear:animated]; [[AFHTTPSessionManager manager] GET:@"http://api.map.baidu.com/telematics/v3/weather?location=%E5%98%89%E5%85%B4&output=json&ak=5slgyqGDENN7Sy7pw29IUvrZ" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC )), dispatch_get_main_queue(), ^{ [_progressView flush:downloadProgress.fractionCompleted]; }); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { }]; } - (void )didReceiveMemoryWarning { [super didReceiveMemoryWarning]; }
相应的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void )receiveWithNotification:(NSNotification *)notifcation { dispatch_async (dispatch_get_main_queue(), ^{ if ([notifcation.object isKindOfClass:[NSProgress class ]]) { NSProgress *progress = (NSProgress *)notifcation.object; [self .progressBarlayer flush:progress.fractionCompleted]; } }); } - (void )flush:(CGFloat )progress { [self .progressBarlayer flush:progress]; }
效果图
DEMO
参考
Progress and KVO never called
Wiki-分块传输编码
MDN - Transfer-Encoding
http协议中content-length 以及chunked编码分析