(1)使用 NSURLConnection 直接方式
(2)使用 NSURLConnection 代理方式
(3)使用 NSURLSession 直接方式
(4)使用 NSURLSession 代理方式
(5)使用 AFNetworking 方式
附加功能:
(1)使用 AFNetworking 中的 AFNetworkReachabilityManager 来检查网络情况:
- AFNetworkReachabilityStatusReachableViaWiFi:Wi-Fi 网络下
- AFNetworkReachabilityStatusReachableViaWWAN:2G/3G/4G 蜂窝移动网络下
- AFNetworkReachabilityStatusNotReachable:未连接网络
(2)使用 AFNetworking 中的 AFNetworkActivityIndicatorManager 来启动网络活动指示器:
1 #import "AFNetworkActivityIndicatorManager.h" 2 3 //启动网络活动指示器;会根据网络交互情况,实时显示或隐藏网络活动指示器;他通过「通知与消息机制」来实现 [UIApplication sharedApplication].networkActivityIndicatorVisible 的控制 4 [AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
效果如下:
ViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface ViewController : UITableViewController 4 @property (copy, nonatomic) NSArray *arrSampleName; 5 6 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName; 7 8 @end
ViewController.m
1 #import "ViewController.h" 2 #import "NSURLConnectionViewController.h" 3 #import "NSURLConnectionDelegateViewController.h" 4 #import "NSURLSessionViewController.h" 5 #import "NSURLSessionDelegateViewController.h" 6 #import "AFNetworkingViewController.h" 7 8 @interface ViewController () 9 - (void)layoutUI; 10 @end 11 12 @implementation ViewController 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 [self layoutUI]; 17 } 18 19 - (void)didReceiveMemoryWarning { 20 [super didReceiveMemoryWarning]; 21 // Dispose of any resources that can be recreated. 22 } 23 24 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName { 25 if (self = [super initWithStyle:UITableViewStyleGrouped]) { 26 self.navigationItem.title = @"多种方式实现文件下载功能"; 27 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil]; 28 29 _arrSampleName = arrSampleName; 30 } 31 return self; 32 } 33 34 - (void)layoutUI { 35 } 36 37 #pragma mark - UITableViewController相关方法重写 38 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 39 return 0.1; 40 } 41 42 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 43 return 1; 44 } 45 46 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 47 return [_arrSampleName count]; 48 } 49 50 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 51 static NSString *cellIdentifier = @"cell"; 52 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 53 if (!cell) { 54 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 55 } 56 cell.textLabel.text = _arrSampleName[indexPath.row]; 57 return cell; 58 } 59 60 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 61 switch (indexPath.row) { 62 case 0: { 63 NSURLConnectionViewController *connectionVC = [NSURLConnectionViewController new]; 64 [self.navigationController pushViewController:connectionVC animated:YES]; 65 break; 66 } 67 case 1: { 68 NSURLConnectionDelegateViewController *connectionDelegateVC = [NSURLConnectionDelegateViewController new]; 69 [self.navigationController pushViewController:connectionDelegateVC animated:YES]; 70 break; 71 } 72 case 2: { 73 NSURLSessionViewController *sessionVC = [NSURLSessionViewController new]; 74 [self.navigationController pushViewController:sessionVC animated:YES]; 75 break; 76 } 77 case 3: { 78 NSURLSessionDelegateViewController *sessionDelegateVC = [NSURLSessionDelegateViewController new]; 79 [self.navigationController pushViewController:sessionDelegateVC animated:YES]; 80 break; 81 } 82 case 4: { 83 AFNetworkingViewController *networkingVC = [AFNetworkingViewController new]; 84 [self.navigationController pushViewController:networkingVC animated:YES]; 85 break; 86 } 87 default: 88 break; 89 } 90 } 91 92 @end
PrefixHeader.pch
1 #define kFileURLStr @"http://files.cnblogs.com/files/huangjianwu/metro_demo使用Highcharts实现图表展示.zip" 2 3 #define kTitleOfNSURLConnection @"使用 NSURLConnection 直接方式" 4 #define kTitleOfNSURLConnectionDelegate @"使用 NSURLConnection 代理方式" 5 #define kTitleOfNSURLSession @"使用 NSURLSession 直接方式" 6 #define kTitleOfNSURLSessionDelegate @"使用 NSURLSession 代理方式" 7 #define kTitleOfAFNetworking @"使用 AFNetworking 方式" 8 9 #define kApplication [UIApplication sharedApplication]
UIButton+BeautifulButton.h
1 #import <UIKit/UIKit.h> 2 3 @interface UIButton (BeautifulButton) 4 /** 5 * 根据按钮文字颜色,返回对应文字颜色的圆角按钮 6 * 7 * @param tintColor 按钮文字颜色;nil 的话就为深灰色 8 */ 9 - (void)beautifulButton:(UIColor *)tintColor; 10 11 @end
UIButton+BeautifulButton.m
1 #import "UIButton+BeautifulButton.h" 2 3 @implementation UIButton (BeautifulButton) 4 5 - (void)beautifulButton:(UIColor *)tintColor { 6 self.tintColor = tintColor ?: [UIColor darkGrayColor]; 7 self.layer.masksToBounds = YES; 8 self.layer.cornerRadius = 10.0; 9 self.layer.borderColor = [UIColor grayColor].CGColor; 10 self.layer.borderWidth = 1.0; 11 } 12 13 @end
NSURLConnectionViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface NSURLConnectionViewController : UIViewController 4 @property (strong, nonatomic) IBOutlet UILabel *lblFileName; 5 @property (strong, nonatomic) IBOutlet UILabel *lblMessage; 6 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFile; 7 8 @end
NSURLConnectionViewController.m
1 #import "NSURLConnectionViewController.h" 2 #import "UIButton+BeautifulButton.h" 3 4 @interface NSURLConnectionViewController () 5 - (void)layoutUI; 6 - (void)saveDataToDisk:(NSData *)data; 7 @end 8 9 @implementation NSURLConnectionViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 14 [self layoutUI]; 15 } 16 17 - (void)didReceiveMemoryWarning { 18 [super didReceiveMemoryWarning]; 19 // Dispose of any resources that can be recreated. 20 } 21 22 - (void)layoutUI { 23 self.navigationItem.title = kTitleOfNSURLConnection; 24 self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.000]; 25 26 [_btnDownloadFile beautifulButton:nil]; 27 } 28 29 - (void)saveDataToDisk:(NSData *)data { 30 //数据接收完保存文件;注意苹果官方要求:下载数据只能保存在缓存目录(/Library/Caches) 31 NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 32 savePath = [savePath stringByAppendingPathComponent:_lblFileName.text]; 33 [data writeToFile:savePath atomically:YES]; //writeToFile: 方法:如果 savePath 文件存在,他会执行覆盖 34 } 35 36 - (IBAction)downloadFile:(id)sender { 37 _lblMessage.text = @"下载中..."; 38 39 NSString *fileURLStr = kFileURLStr; 40 //编码操作;对应的解码操作是用 stringByRemovingPercentEncoding 方法 41 fileURLStr = [fileURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 42 NSURL *fileURL = [NSURL URLWithString:fileURLStr]; 43 44 //创建请求 45 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; 46 47 //创建连接;Apple 提供的处理一般请求的两种方法,他们不需要进行一系列的 NSURLConnectionDataDelegate 委托协议方法操作,简洁直观 48 //方法一:发送一个同步请求;不建议使用,因为当前线程是主线程的话,会造成线程阻塞,一般比较少用 49 // NSURLResponse *response; 50 // NSError *connectionError; 51 // NSData *data = [NSURLConnection sendSynchronousRequest:request 52 // returningResponse:&response 53 // error:&connectionError]; 54 // if (!connectionError) { 55 // [self saveDataToDisk:data]; 56 // NSLog(@"保存成功"); 57 // 58 // _lblMessage.text = @"下载完成"; 59 // } else { 60 // NSLog(@"下载失败,错误信息:%@", connectionError.localizedDescription); 61 // 62 // _lblMessage.text = @"下载失败"; 63 // } 64 65 //方法二:发送一个异步请求 66 [NSURLConnection sendAsynchronousRequest:request 67 queue:[NSOperationQueue mainQueue] 68 completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 69 if (!connectionError) { 70 [self saveDataToDisk:data]; 71 NSLog(@"保存成功"); 72 73 _lblMessage.text = @"下载完成"; 74 75 } else { 76 NSLog(@"下载失败,错误信息:%@", connectionError.localizedDescription); 77 78 _lblMessage.text = @"下载失败"; 79 } 80 }]; 81 } 82 83 @end
NSURLConnectionViewController.xib
NSURLConnectionDelegateViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface NSURLConnectionDelegateViewController : UIViewController 4 @property (strong, nonatomic) NSMutableData *mDataReceive; 5 @property (assign, nonatomic) NSUInteger totalDataLength; 6 7 @property (strong, nonatomic) IBOutlet UILabel *lblFileName; 8 @property (strong, nonatomic) IBOutlet UIProgressView *progVDownloadFile; 9 @property (strong, nonatomic) IBOutlet UILabel *lblMessage; 10 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFile; 11 12 @end
NSURLConnectionDelegateViewController.m
1 #import "NSURLConnectionDelegateViewController.h" 2 #import "UIButton+BeautifulButton.h" 3 4 @interface NSURLConnectionDelegateViewController () 5 - (void)layoutUI; 6 - (BOOL)isExistCacheInMemory:(NSURLRequest *)request; 7 - (void)updateProgress; 8 @end 9 10 @implementation NSURLConnectionDelegateViewController 11 12 - (void)viewDidLoad { 13 [super viewDidLoad]; 14 15 [self layoutUI]; 16 } 17 18 - (void)didReceiveMemoryWarning { 19 [super didReceiveMemoryWarning]; 20 // Dispose of any resources that can be recreated. 21 } 22 23 - (void)layoutUI { 24 self.navigationItem.title = kTitleOfNSURLConnectionDelegate; 25 self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.000]; 26 27 [_btnDownloadFile beautifulButton:nil]; 28 } 29 30 - (BOOL)isExistCacheInMemory:(NSURLRequest *)request { 31 BOOL isExistCache = NO; 32 NSURLCache *cache = [NSURLCache sharedURLCache]; 33 [cache setMemoryCapacity:1024 * 1024]; //1M 34 35 NSCachedURLResponse *response = [cache cachedResponseForRequest:request]; 36 if (response != nil) { 37 NSLog(@"内存中存在对应请求的响应缓存"); 38 isExistCache = YES; 39 } 40 return isExistCache; 41 } 42 43 - (void)updateProgress { 44 NSUInteger receiveDataLength = _mDataReceive.length; 45 if (receiveDataLength == _totalDataLength) { 46 _lblMessage.text = @"下载完成"; 47 kApplication.networkActivityIndicatorVisible = NO; 48 } else { 49 _lblMessage.text = @"下载中..."; 50 kApplication.networkActivityIndicatorVisible = YES; 51 _progVDownloadFile.progress = (float)receiveDataLength / _totalDataLength; 52 } 53 } 54 55 - (IBAction)downloadFile:(id)sender { 56 /* 57 此例子更多的是希望大家了解代理方法接收响应数据的过程,实际开发中也不可能使用这种方法进行文件下载。这种下载有个致命的问题:无法进行大文件下载。因为代理方法在接收数据时虽然表面看起来是每次读取一部分响应数据,事实上它只有一次请求并且也只接收了一次服务器响应,只是当响应数据较大时系统会重复调用数据接收方法,每次将已读取的数据拿出一部分交给数据接收方法而已。在这个过程中其实早已经将响应数据全部拿到,只是分批交给开发者而已。这样一来对于几个G的文件如果进行下载,那么不用说是真机下载了,就算是模拟器恐怕也是不现实的。 58 实际开发文件下载的时候不管是通过代理方法还是静态方法执行请求和响应,我们都会分批请求数据,而不是一次性请求数据。假设一个文件有1G,那么只要每次请求1M的数据,请求1024次也就下载完了。那么如何让服务器每次只返回1M的数据呢? 59 在网络开发中可以在请求的头文件中设置一个Range信息,它代表请求数据的大小。通过这个字段配合服务器端可以精确的控制每次服务器响应的数据范围。例如指定bytes=0-1023,然后在服务器端解析Range信息,返回该文件的0到1023之间的数据的数据即可(共1024Byte)。这样,只要在每次发送请求控制这个头文件信息就可以做到分批请求。 60 当然,为了让整个数据保持完整,每次请求的数据都需要逐步追加直到整个文件请求完成。但是如何知道整个文件的大小?其实在此例子通过头文件信息获取整个文件大小,他请求整个数据,这样做对分段下载就没有任何意义了。所幸在WEB开发中我们还有另一种请求方法“HEAD”,通过这种请求服务器只会响应头信息,其他数据不会返回给客户端,这样一来整个数据的大小也就可以得到了。 61 */ 62 63 64 NSString *fileURLStr = kFileURLStr; 65 //编码操作;对应的解码操作是用 stringByRemovingPercentEncoding 方法 66 fileURLStr = [fileURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 67 NSURL *fileURL = [NSURL URLWithString:fileURLStr]; 68 69 /*创建请求 70 cachePolicy:缓存策略 71 1、NSURLRequestUseProtocolCachePolicy 协议缓存,根据 response 中的 Cache-Control 字段判断缓存是否有效,如果缓存有效则使用缓存数据否则重新从服务器请求 72 2、NSURLRequestReloadIgnoringLocalCacheData 不使用缓存,直接请求新数据 73 3、NSURLRequestReloadIgnoringCacheData 等同于 NSURLRequestReloadIgnoringLocalCacheData 74 4、NSURLRequestReturnCacheDataElseLoad 直接使用缓存数据不管是否有效,没有缓存则重新请求 75 5、NSURLRequestReturnCacheDataDontLoad 直接使用缓存数据不管是否有效,没有缓存数据则失败 76 77 timeoutInterval:超时时间设置(默认60s) 78 */ 79 NSURLRequest *request = [[NSURLRequest alloc] initWithURL:fileURL 80 cachePolicy:NSURLRequestUseProtocolCachePolicy 81 timeoutInterval:60.0]; 82 if ([self isExistCacheInMemory:request]) { 83 request = [[NSURLRequest alloc] initWithURL:fileURL 84 cachePolicy:NSURLRequestReturnCacheDataDontLoad 85 timeoutInterval:60.0]; 86 } 87 88 //创建连接,异步操作 89 NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request 90 delegate:self]; 91 [connection start]; //启动连接 92 } 93 94 #pragma mark - NSURLConnectionDataDelegate 95 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response { 96 NSLog(@"即将发送请求"); 97 98 return request; 99 } 100 101 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 102 NSLog(@"已经接收到响应"); 103 104 _mDataReceive = [NSMutableData new]; 105 _progVDownloadFile.progress = 0.0; 106 107 //通过响应头中的 Content-Length 获取到整个响应的总长度 108 /* 109 { 110 "Accept-Ranges" = bytes; 111 "Cache-Control" = "max-age=7776000"; 112 "Content-Length" = 592441; 113 "Content-Type" = "application/x-zip-compressed"; 114 Date = "Wed, 02 Sep 2015 13:17:01 GMT"; 115 Etag = "\"d8f617371f9cd01:0\""; 116 "Last-Modified" = "Mon, 01 Jun 2015 03:58:27 GMT"; 117 Server = "Microsoft-IIS/7.5"; 118 "X-Powered-By" = "ASP.NET"; 119 } 120 */ 121 NSDictionary *dicHeaderField = [(NSHTTPURLResponse *)response allHeaderFields]; 122 _totalDataLength = [[dicHeaderField objectForKey:@"Content-Length"] integerValue]; 123 } 124 125 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 126 NSLog(@"已经接收到响应数据,数据长度为%lu字节...", (unsigned long)[data length]); 127 128 [_mDataReceive appendData:data]; //连续接收数据 129 [self updateProgress]; //连续更新进度条 130 } 131 132 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 133 NSLog(@"已经接收完所有响应数据"); 134 135 NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 136 savePath = [savePath stringByAppendingPathComponent:_lblFileName.text]; 137 [_mDataReceive writeToFile:savePath atomically:YES]; 138 } 139 140 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 141 //如果连接超时或者连接地址错误可能就会报错 142 NSLog(@"连接错误,错误信息:%@", error.localizedDescription); 143 144 _lblMessage.text = @"连接错误"; 145 } 146 147 @end
NSURLConnectionDelegateViewController.xib
NSURLSessionViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface NSURLSessionViewController : UIViewController 4 @property (strong, nonatomic) IBOutlet UILabel *lblFileName; 5 @property (strong, nonatomic) IBOutlet UILabel *lblMessage; 6 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFile; 7 8 @end
NSURLSessionViewController.m
1 #import "NSURLSessionViewController.h" 2 #import "UIButton+BeautifulButton.h" 3 4 @interface NSURLSessionViewController () 5 - (void)layoutUI; 6 @end 7 8 @implementation NSURLSessionViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 [self layoutUI]; 14 } 15 16 - (void)didReceiveMemoryWarning { 17 [super didReceiveMemoryWarning]; 18 // Dispose of any resources that can be recreated. 19 } 20 21 - (void)layoutUI { 22 self.navigationItem.title = kTitleOfNSURLSession; 23 self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.000]; 24 25 [_btnDownloadFile beautifulButton:nil]; 26 } 27 28 - (IBAction)downloadFile:(id)sender { 29 _lblMessage.text = @"下载中..."; 30 31 NSString *fileURLStr = kFileURLStr; 32 //编码操作;对应的解码操作是用 stringByRemovingPercentEncoding 方法 33 fileURLStr = [fileURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 34 NSURL *fileURL = [NSURL URLWithString:fileURLStr]; 35 36 //创建请求 37 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; 38 39 //创建会话(这里使用了一个全局会话) 40 NSURLSession *session = [NSURLSession sharedSession]; 41 42 //创建下载任务,并且启动他;在非主线程中执行 43 NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { 44 __block void (^updateUI)(); //声明用于主线程更新 UI 的代码块 45 46 if (!error) { 47 NSLog(@"下载后的临时保存路径:%@", location); 48 49 NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 50 savePath = [savePath stringByAppendingPathComponent:_lblFileName.text]; 51 NSURL *saveURL = [NSURL fileURLWithPath:savePath]; 52 NSError *saveError; 53 NSFileManager *fileManager = [NSFileManager defaultManager]; 54 //判断是否存在旧的目标文件,如果存在就先移除;避免无法复制问题 55 if ([fileManager fileExistsAtPath:savePath]) { 56 [fileManager removeItemAtPath:savePath error:&saveError]; 57 if (saveError) { 58 NSLog(@"移除旧的目标文件失败,错误信息:%@", saveError.localizedDescription); 59 60 updateUI = ^ { 61 _lblMessage.text = @"下载失败"; 62 }; 63 } 64 } 65 if (!saveError) { 66 //把源文件复制到目标文件,当目标文件存在时,会抛出一个错误到 error 参数指向的对象实例 67 //方法一(path 不能有 file:// 前缀) 68 // [fileManager copyItemAtPath:[location path] 69 // toPath:savePath 70 // error:&saveError]; 71 72 //方法二 73 [fileManager copyItemAtURL:location 74 toURL:saveURL 75 error:&saveError]; 76 77 if (!saveError) { 78 NSLog(@"保存成功"); 79 80 updateUI = ^ { 81 _lblMessage.text = @"下载完成"; 82 }; 83 } else { 84 NSLog(@"保存失败,错误信息:%@", saveError.localizedDescription); 85 86 updateUI = ^ { 87 _lblMessage.text = @"下载失败"; 88 }; 89 } 90 } 91 92 } else { 93 NSLog(@"下载失败,错误信息:%@", error.localizedDescription); 94 95 updateUI = ^ { 96 _lblMessage.text = @"下载失败"; 97 }; 98 } 99 100 dispatch_async(dispatch_get_main_queue(), updateUI); //使用主队列异步方式(主线程)执行更新 UI 的代码块 101 }]; 102 [downloadTask resume]; //恢复线程,启动任务 103 } 104 105 @end
NSURLSessionViewController.xib
NSURLSessionDelegateViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface NSURLSessionDelegateViewController : UIViewController <NSURLSessionDownloadDelegate> 4 @property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask; 5 6 @property (strong, nonatomic) IBOutlet UILabel *lblFileName; 7 @property (strong, nonatomic) IBOutlet UIProgressView *progVDownloadFile; 8 @property (strong, nonatomic) IBOutlet UILabel *lblMessage; 9 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFile; 10 @property (strong, nonatomic) IBOutlet UIButton *btnCancel; 11 @property (strong, nonatomic) IBOutlet UIButton *btnSuspend; 12 @property (strong, nonatomic) IBOutlet UIButton *btnResume; 13 14 @end
NSURLSessionDelegateViewController.m
1 #import "NSURLSessionDelegateViewController.h" 2 #import "UIButton+BeautifulButton.h" 3 4 @interface NSURLSessionDelegateViewController () 5 - (void)layoutUI; 6 - (NSURLSession *)defaultSession; 7 - (NSURLSession *)backgroundSession; 8 - (void)updateProgress:(int64_t)receiveDataLength totalDataLength:(int64_t)totalDataLength; 9 @end 10 11 @implementation NSURLSessionDelegateViewController 12 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 [self layoutUI]; 17 } 18 19 - (void)didReceiveMemoryWarning { 20 [super didReceiveMemoryWarning]; 21 // Dispose of any resources that can be recreated. 22 } 23 24 - (void)layoutUI { 25 self.navigationItem.title = kTitleOfNSURLSessionDelegate; 26 self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.000]; 27 28 [_btnDownloadFile beautifulButton:nil]; 29 [_btnCancel beautifulButton:[UIColor redColor]]; 30 [_btnSuspend beautifulButton:[UIColor purpleColor]]; 31 [_btnResume beautifulButton:[UIColor orangeColor]]; 32 } 33 34 - (NSURLSession *)defaultSession { 35 /* 36 NSURLSession 支持进程三种会话: 37 1、defaultSessionConfiguration:进程内会话(默认会话),用硬盘来缓存数据。 38 2、ephemeralSessionConfiguration:临时的进程内会话(内存),不会将 cookie、缓存储存到本地,只会放到内存中,当应用程序退出后数据也会消失。 39 3、backgroundSessionConfiguration:后台会话,相比默认会话,该会话会在后台开启一个线程进行网络数据处理。 40 */ 41 42 //创建会话配置「进程内会话」 43 NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; 44 sessionConfiguration.timeoutIntervalForRequest = 60.0; //请求超时时间;默认为60秒 45 sessionConfiguration.allowsCellularAccess = YES; //是否允许蜂窝网络访问(2G/3G/4G) 46 sessionConfiguration.HTTPMaximumConnectionsPerHost = 4; //限制每次最多连接数;在 iOS 中默认值为4 47 48 //创建会话 49 NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration 50 delegate:self 51 delegateQueue:nil]; 52 return session; 53 } 54 55 - (NSURLSession *)backgroundSession { 56 static NSURLSession *session; 57 static dispatch_once_t onceToken; 58 dispatch_once(&onceToken, ^{ //应用程序生命周期内,只执行一次;保证只有一个「后台会话」 59 //创建会话配置「后台会话」 60 NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"KMDownloadFile.NSURLSessionDelegateViewController"]; 61 sessionConfiguration.timeoutIntervalForRequest = 60.0; //请求超时时间;默认为60秒 62 sessionConfiguration.allowsCellularAccess = YES; //是否允许蜂窝网络访问(2G/3G/4G) 63 sessionConfiguration.HTTPMaximumConnectionsPerHost = 4; //限制每次最多连接数;在 iOS 中默认值为4 64 sessionConfiguration.discretionary = YES; //是否自动选择最佳网络访问,仅对「后台会话」有效 65 66 //创建会话 67 session = [NSURLSession sessionWithConfiguration:sessionConfiguration 68 delegate:self 69 delegateQueue:nil]; 70 }); 71 return session; 72 } 73 74 - (void)updateProgress:(int64_t)receiveDataLength totalDataLength:(int64_t)totalDataLength; { 75 dispatch_async(dispatch_get_main_queue(), ^{ //使用主队列异步方式(主线程)执行更新 UI 操作 76 if (receiveDataLength == totalDataLength) { 77 _lblMessage.text = @"下载完成"; 78 kApplication.networkActivityIndicatorVisible = NO; 79 } else { 80 _lblMessage.text = @"下载中..."; 81 kApplication.networkActivityIndicatorVisible = YES; 82 _progVDownloadFile.progress = (float)receiveDataLength / totalDataLength; 83 } 84 }); 85 } 86 87 - (IBAction)downloadFile:(id)sender { 88 NSString *fileURLStr = kFileURLStr; 89 //编码操作;对应的解码操作是用 stringByRemovingPercentEncoding 方法 90 fileURLStr = [fileURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 91 NSURL *fileURL = [NSURL URLWithString:fileURLStr]; 92 93 //创建请求 94 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; 95 96 //创建会话「进程内会话」;如要用「后台会话」就使用自定义的[self backgroundSession] 方法 97 NSURLSession *session = [self defaultSession]; 98 99 //创建下载任务,并且启动他;在非主线程中执行 100 _downloadTask = [session downloadTaskWithRequest:request]; 101 [_downloadTask resume]; 102 103 /* 104 会话任务状态 105 typedef NS_ENUM(NSInteger, NSURLSessionTaskState) { 106 NSURLSessionTaskStateRunning = 0, //正在执行 107 NSURLSessionTaskStateSuspended = 1, //已挂起 108 NSURLSessionTaskStateCanceling = 2, //正在取消 109 NSURLSessionTaskStateCompleted = 3, //已完成 110 } NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0); 111 */ 112 } 113 114 - (IBAction)cancel:(id)sender { 115 [_downloadTask cancel]; 116 } 117 118 - (IBAction)suspend:(id)sender { 119 [_downloadTask suspend]; 120 kApplication.networkActivityIndicatorVisible = NO; 121 } 122 123 - (IBAction)resume:(id)sender { 124 [_downloadTask resume]; 125 } 126 127 #pragma mark - NSURLSessionDownloadDelegate 128 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { 129 NSLog(@"已经接收到响应数据,数据长度为%lld字节...", totalBytesWritten); 130 131 [self updateProgress:totalBytesWritten totalDataLength:totalBytesExpectedToWrite]; 132 } 133 134 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { 135 //下载文件会临时保存,正常流程下系统最终会自动清除此临时文件;保存路径目录根据会话类型而有所不同: 136 //「进程内会话(默认会话)」和「临时的进程内会话(内存)」,路径目录为:/tmp,可以通过 NSTemporaryDirectory() 方法获取 137 //「后台会话」,路径目录为:/Library/Caches/com.apple.nsurlsessiond/Downloads/com.kenmu.KMDownloadFile 138 NSLog(@"已经接收完所有响应数据,下载后的临时保存路径:%@", location); 139 140 __block void (^updateUI)(); //声明用于主线程更新 UI 的代码块 141 142 NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 143 savePath = [savePath stringByAppendingPathComponent:_lblFileName.text]; 144 NSURL *saveURL = [NSURL fileURLWithPath:savePath]; 145 NSError *saveError; 146 NSFileManager *fileManager = [NSFileManager defaultManager]; 147 //判断是否存在旧的目标文件,如果存在就先移除;避免无法复制问题 148 if ([fileManager fileExistsAtPath:savePath]) { 149 [fileManager removeItemAtPath:savePath error:&saveError]; 150 if (saveError) { 151 NSLog(@"移除旧的目标文件失败,错误信息:%@", saveError.localizedDescription); 152 153 updateUI = ^ { 154 _lblMessage.text = @"下载失败"; 155 }; 156 } 157 } 158 if (!saveError) { 159 //把源文件复制到目标文件,当目标文件存在时,会抛出一个错误到 error 参数指向的对象实例 160 //方法一(path 不能有 file:// 前缀) 161 // [fileManager copyItemAtPath:[location path] 162 // toPath:savePath 163 // error:&saveError]; 164 165 //方法二 166 [fileManager copyItemAtURL:location 167 toURL:saveURL 168 error:&saveError]; 169 170 if (!saveError) { 171 NSLog(@"保存成功"); 172 173 updateUI = ^ { 174 _lblMessage.text = @"下载完成"; 175 }; 176 } else { 177 NSLog(@"保存失败,错误信息:%@", saveError.localizedDescription); 178 179 updateUI = ^ { 180 _lblMessage.text = @"下载失败"; 181 }; 182 } 183 } 184 185 dispatch_async(dispatch_get_main_queue(), updateUI); //使用主队列异步方式(主线程)执行更新 UI 的代码块 186 } 187 188 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 189 NSLog(@"无论下载成功还是失败,最终都会执行一次"); 190 191 if (error) { 192 NSString *desc = error.localizedDescription; 193 NSLog(@"下载失败,错误信息:%@", desc); 194 195 dispatch_async(dispatch_get_main_queue(), ^{ //使用主队列异步方式(主线程)执行更新 UI 操作 196 _lblMessage.text = [desc isEqualToString:@"cancelled"] ? @"下载已取消" : @"下载失败"; 197 kApplication.networkActivityIndicatorVisible = NO; 198 _progVDownloadFile.progress = 0.0; 199 }); 200 } 201 } 202 203 @end
NSURLSessionDelegateViewController.xib
AFNetworkingViewController.h
1 #import <UIKit/UIKit.h> 2 #import "MBProgressHUD.h" 3 4 @interface AFNetworkingViewController : UIViewController 5 @property (strong, nonatomic) MBProgressHUD *hud; 6 7 @property (strong, nonatomic) IBOutlet UILabel *lblFileName; 8 @property (strong, nonatomic) IBOutlet UILabel *lblMessage; 9 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFileByConnection; 10 @property (strong, nonatomic) IBOutlet UIButton *btnDownloadFileBySession; 11 12 @end
AFNetworkingViewController.m
1 #import "AFNetworkingViewController.h" 2 #import "AFNetworking.h" 3 #import "AFNetworkActivityIndicatorManager.h" 4 #import "UIButton+BeautifulButton.h" 5 6 @interface AFNetworkingViewController () 7 - (void)showAlert:(NSString *)msg; 8 - (void)checkNetwork; 9 - (void)layoutUI; 10 - (NSMutableURLRequest *)downloadRequest; 11 - (NSURL *)saveURL:(NSURLResponse *)response deleteExistFile:(BOOL)deleteExistFile; 12 - (void)updateProgress:(int64_t)receiveDataLength totalDataLength:(int64_t)totalDataLength; 13 @end 14 15 @implementation AFNetworkingViewController 16 17 - (void)viewDidLoad { 18 [super viewDidLoad]; 19 20 [self layoutUI]; 21 } 22 23 - (void)didReceiveMemoryWarning { 24 [super didReceiveMemoryWarning]; 25 // Dispose of any resources that can be recreated. 26 } 27 28 - (void)showAlert:(NSString *)msg { 29 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"网络情况" 30 message:msg 31 delegate:self 32 cancelButtonTitle:nil 33 otherButtonTitles:@"确定", nil]; 34 [alert show]; 35 } 36 37 - (void)checkNetwork { 38 NSURL *baseURL = [NSURL URLWithString:@"http://www.baidu.com/"]; 39 AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL]; 40 41 NSOperationQueue *operationQueue = manager.operationQueue; 42 [manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { 43 switch (status) { 44 case AFNetworkReachabilityStatusReachableViaWiFi: 45 [self showAlert:@"Wi-Fi 网络下"]; 46 [operationQueue setSuspended:NO]; 47 break; 48 case AFNetworkReachabilityStatusReachableViaWWAN: 49 [self showAlert:@"2G/3G/4G 蜂窝移动网络下"]; 50 [operationQueue setSuspended:YES]; 51 break; 52 case AFNetworkReachabilityStatusNotReachable: 53 default: 54 [self showAlert:@"未连接网络"]; 55 [operationQueue setSuspended:YES]; 56 break; 57 } 58 }]; 59 60 [manager.reachabilityManager startMonitoring]; 61 } 62 63 - (void)layoutUI { 64 self.navigationItem.title = kTitleOfAFNetworking; 65 self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.000]; 66 67 //条件表达式中,「?:」可以表示在条件不成立的情况,才使用后者赋值,否则使用用于条件判断的前者赋值 68 //以下语句等同于:UIButton *btn = _btnDownloadFileByConnection ? _btnDownloadFileByConnection : [UIButton new]; 69 //在 .NET 中,相当于使用「??」;在 JavaScript 中,相当于使用「||」来实现这种类似的判断 70 UIButton *btn = _btnDownloadFileByConnection ?: [UIButton new]; 71 [btn beautifulButton:nil]; 72 [_btnDownloadFileBySession beautifulButton:[UIColor orangeColor]]; 73 74 //进度效果 75 _hud = [[MBProgressHUD alloc] initWithView:self.view]; 76 _hud.mode = MBProgressHUDModeDeterminate; 77 _hud.labelText = @"下载中..."; 78 [_hud hide:YES]; 79 [self.view addSubview:_hud]; 80 81 //检查网络情况 82 [self checkNetwork]; 83 84 //启动网络活动指示器;会根据网络交互情况,实时显示或隐藏网络活动指示器;他通过「通知与消息机制」来实现 [UIApplication sharedApplication].networkActivityIndicatorVisible 的控制 85 [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; 86 } 87 88 - (NSMutableURLRequest *)downloadRequest { 89 NSString *fileURLStr = kFileURLStr; 90 //编码操作;对应的解码操作是用 stringByRemovingPercentEncoding 方法 91 fileURLStr = [fileURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 92 NSURL *fileURL = [NSURL URLWithString:fileURLStr]; 93 94 //创建请求 95 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL]; 96 return request; 97 } 98 99 - (NSURL *)saveURL:(NSURLResponse *)response deleteExistFile:(BOOL)deleteExistFile { 100 NSString *fileName = response ? [response suggestedFilename] : _lblFileName.text; 101 102 //方法一 103 // NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 104 // savePath = [savePath stringByAppendingPathComponent:fileName]; 105 // NSURL *saveURL = [NSURL fileURLWithPath:savePath]; 106 107 //方法二 108 NSURL *saveURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; 109 saveURL = [saveURL URLByAppendingPathComponent:fileName]; 110 NSString *savePath = [saveURL path]; 111 112 if (deleteExistFile) { 113 NSError *saveError; 114 NSFileManager *fileManager = [NSFileManager defaultManager]; 115 //判断是否存在旧的目标文件,如果存在就先移除;避免无法复制问题 116 if ([fileManager fileExistsAtPath:savePath]) { 117 [fileManager removeItemAtPath:savePath error:&saveError]; 118 if (saveError) { 119 NSLog(@"移除旧的目标文件失败,错误信息:%@", saveError.localizedDescription); 120 } 121 } 122 } 123 124 return saveURL; 125 } 126 127 - (void)updateProgress:(int64_t)receiveDataLength totalDataLength:(int64_t)totalDataLength; { 128 dispatch_async(dispatch_get_main_queue(), ^{ //使用主队列异步方式(主线程)执行更新 UI 操作 129 _hud.progress = (float)receiveDataLength / totalDataLength; 130 131 if (receiveDataLength == totalDataLength) { 132 _lblMessage.text = receiveDataLength < 0 ? @"下载失败" : @"下载完成"; 133 //kApplication.networkActivityIndicatorVisible = NO; 134 [_hud hide:YES]; 135 } else { 136 _lblMessage.text = @"下载中..."; 137 //kApplication.networkActivityIndicatorVisible = YES; 138 [_hud show:YES]; 139 } 140 }); 141 } 142 143 - (IBAction)downloadFileByConnection:(id)sender { 144 //创建请求 145 NSMutableURLRequest *request = [self downloadRequest]; 146 147 //创建请求操作 148 AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 149 NSString *savePath = [[self saveURL:nil deleteExistFile:NO] path]; 150 151 [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) { 152 NSLog(@"已经接收到响应数据,数据长度为%lld字节...", totalBytesRead); 153 154 [self updateProgress:totalBytesRead totalDataLength:totalBytesExpectedToRead]; 155 }]; 156 157 [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 158 NSLog(@"已经接收完所有响应数据"); 159 160 NSData *data = (NSData *)responseObject; 161 [data writeToFile:savePath atomically:YES]; //responseObject 的对象类型是 NSData 162 163 [self updateProgress:100 totalDataLength:100]; 164 } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 165 NSLog(@"下载失败,错误信息:%@", error.localizedDescription); 166 167 [self updateProgress:-1 totalDataLength:-1]; 168 }]; 169 170 //启动请求操作 171 [operation start]; 172 } 173 174 - (IBAction)downloadFileBySession:(id)sender { 175 //创建请求 176 NSMutableURLRequest *request = [self downloadRequest]; 177 178 //创建会话配置「进程内会话」 179 NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; 180 sessionConfiguration.timeoutIntervalForRequest = 60.0; //请求超时时间;默认为60秒 181 sessionConfiguration.allowsCellularAccess = YES; //是否允许蜂窝网络访问(2G/3G/4G) 182 sessionConfiguration.HTTPMaximumConnectionsPerHost = 4; //限制每次最多连接数;在 iOS 中默认值为4 183 184 //创建会话管理器 185 AFURLSessionManager *sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration]; 186 187 //创建会话下载任务,并且启动他;在非主线程中执行 188 NSURLSessionDownloadTask *task = [sessionManager 189 downloadTaskWithRequest:request 190 progress:nil 191 destination:^ NSURL*(NSURL *targetPath, NSURLResponse *response) { 192 //当 sessionManager 调用 setDownloadTaskDidFinishDownloadingBlock: 方法,并且方法代码块返回值不为 nil 时(优先级高),下面的两句代码是不执行的(优先级低) 193 NSLog(@"下载后的临时保存路径:%@", targetPath); 194 return [self saveURL:response deleteExistFile:YES]; 195 } completionHandler:^ (NSURLResponse *response, NSURL *filePath, NSError *error) { 196 if (!error) { 197 NSLog(@"下载后的保存路径:%@", filePath); //为上面代码块返回的路径 198 199 [self updateProgress:100 totalDataLength:100]; 200 } else { 201 NSLog(@"下载失败,错误信息:%@", error.localizedDescription); 202 203 [self updateProgress:-1 totalDataLength:-1]; 204 } 205 206 [_hud hide:YES]; 207 }]; 208 209 //类似 NSURLSessionDownloadDelegate 的方法操作 210 //- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; 211 [sessionManager setDownloadTaskDidWriteDataBlock:^ (NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { 212 NSLog(@"已经接收到响应数据,数据长度为%lld字节...", totalBytesWritten); 213 214 [self updateProgress:totalBytesWritten totalDataLength:totalBytesExpectedToWrite]; 215 }]; 216 217 //- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location; 218 [sessionManager setDownloadTaskDidFinishDownloadingBlock:^ NSURL*(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) { 219 NSLog(@"已经接收完所有响应数据,下载后的临时保存路径:%@", location); 220 return [self saveURL:nil deleteExistFile:YES]; 221 }]; 222 223 [task resume]; 224 } 225 226 @end
AFNetworkingViewController.xib
View Cod
AppDelegate.h
1 #import <UIKit/UIKit.h> 2 3 @interface AppDelegate : UIResponder <UIApplicationDelegate> 4 5 @property (strong, nonatomic) UIWindow *window; 6 @property (strong, nonatomic) UINavigationController *navigationController; 7 8 @end
AppDelegate.m
1 #import "AppDelegate.h" 2 #import "ViewController.h" 3 4 @interface AppDelegate () 5 6 @end 7 8 @implementation AppDelegate 9 10 11 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 12 _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 13 ViewController *viewController = [[ViewController alloc] 14 initWithSampleNameArray:@[ kTitleOfNSURLConnection, 15 kTitleOfNSURLConnectionDelegate, 16 kTitleOfNSURLSession, 17 kTitleOfNSURLSessionDelegate, 18 kTitleOfAFNetworking]]; 19 _navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 20 _window.rootViewController = _navigationController; 21 //[_window addSubview:_navigationController.view]; //当_window.rootViewController关联时,这一句可有可无 22 [_window makeKeyAndVisible]; 23 return YES; 24 } 25 26 - (void)applicationWillResignActive:(UIApplication *)application { 27 } 28 29 - (void)applicationDidEnterBackground:(UIApplication *)application { 30 } 31 32 - (void)applicationWillEnterForeground:(UIApplication *)application { 33 } 34 35 - (void)applicationDidBecomeActive:(UIApplication *)application { 36 } 37 38 - (void)applicationWillTerminate:(UIApplication *)application { 39 } 40 41 @end