网络篇 - 03.NSURLConnection应用场景

来源:互联网 发布:matlab中矩阵运算 编辑:程序博客网 时间:2024/06/06 07:47

1.小文件下载

  • 如果文件比较小,下载方式会比较多
    • 直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;
    • 利用NSURLConnection发送一个HTTP请求去下载
    • 如果是下载图片,还可以利用SDWebImage框架
// 小文件下载-(void)smallDataDownload{    // 1.创建URL    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];    // 2.根据URL创建URL请求对象    NSURLRequest *request = [NSURLRequest requestWithURL:url];    // 3.发送异步请求    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {        // 4.请求完毕后的回调,得到下载的数据        UIImage *image = [UIImage imageWithData:data];        NSLog(@"%@",image);    }];}

2.大文件下载(同时实现断点下载功能)

  • 大文件由于需要较长时间,需要监听其具体下载过程,给用户反馈下载进度,提供良好的用户体验
  • 由于用户不小心关掉程序,重新打开后,用户是希望此前的下载任务能够接着上次的下载进度继续下载的,所以这里也实现了断点下载功能

3.利用NSFileHandle实现大文件下载

  • 这里先使用NSFileHandle实现文件的下载到本地
#import "ViewController.h"#import "NSString+SandboxPath.h"@interface ViewController ()<NSURLConnectionDataDelegate>@property (weak, nonatomic) IBOutlet UIProgressView *progress;@property (nonatomic, strong) NSString *path;// 文件路径@property (nonatomic, assign) NSInteger totalDataSize;// 文件总大小@property (nonatomic, assign) NSInteger curDataLength; // 当前已经下载的文件大小@property (nonatomic, strong) NSFileHandle *fileHandle; // 文件读写操作类@end@implementation ViewController#pragma mark - lazy-(NSFileHandle *)fileHandle{    if (!_fileHandle) {        _fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.path];    }    return _fileHandle;}- (void)viewDidLoad {    [super viewDidLoad];    // 设置文件保存路径    self.path = [@"minion_01.mp4" cacheDir];    NSLog(@"%@",self.path);    // 加载文件    [self largeDataDownLoad];}// 大文件下载-(void)largeDataDownLoad{    // 1.创建URL    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];    // 2.根据URL创建请求对象    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    // 2.1设置请求头从哪里开始下载    // 获取当前下载文件的大小    NSFileManager *manager =[NSFileManager defaultManager];    // 获取当前文件属性    NSDictionary *dict = [manager attributesOfItemAtPath:self.path error:nil];    // 获取当前文件大小    NSString *range = [NSString stringWithFormat:@"bytes:%zd-",[dict[NSFileSize] intValue]];    self.curDataLength = [dict[NSFileSize] intValue];    NSLog(@"%@",range);    // 设置文件下载的范围(范围为当前下载大小到末尾)    [request setValue:range forHTTPHeaderField:@"Range"];    // 2.2发送请求    [NSURLConnection connectionWithRequest:request delegate:self];}#pragma mark - NSURLConnectionDataDelegate// 接收到服务器的响应后调用-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{    // 获取文件总大小    NSInteger totalDataSize = response.expectedContentLength + self.curDataLength;    self.totalDataSize = totalDataSize;    // 创建空文件,用来保存下载的文件,使用文件读写操作类写入下载的数据    NSFileManager *manager = [NSFileManager defaultManager];    // 判断若是文件为空才下创建新文件    if (self.curDataLength == 0) {        [manager createFileAtPath:self.path contents:nil attributes:nil];        NSLog(@"创建文件成功");    }}// 每次接收到服务器的数据后调用(会调用一次或多次)-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{    // 获取当前已经下载的总大小    self.curDataLength += data.length;    // 更新进度条    self.progress.progress = 1.0 * self.curDataLength / self.totalDataSize;    NSLog(@"%f",self.progress.progress);    // 每次下载都移动到当前已下载文件的末尾    [self.fileHandle seekToEndOfFile];    // 写入数据    [self.fileHandle writeData:data];}// 接收数据完成时调用-(void)connectionDidFinishLoading:(NSURLConnection *)connection{    NSLog(@"%s",__func__);    // 下载完成后关闭handle和清空当前下载数据    [self.fileHandle closeFile];    self.curDataLength = 0;}// 接收数据失败调用-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{    NSLog(@"%s",__func__);}

4.利用输出流实现文件的下载和断点下载(NSOutputStream)

#import "ViewController.h"#import "NSString+SandboxPath.h"@interface ViewController ()<NSURLConnectionDataDelegate>@property (weak, nonatomic) IBOutlet UIProgressView *progress;@property (nonatomic, strong) NSString *path;// 文件路径@property (nonatomic, assign) NSInteger totalDataSize;// 文件总大小@property (nonatomic, assign) NSInteger curDataLength; // 当前已经下载的文件大小@property (nonatomic, strong) NSOutputStream *outputStream; // 输出流@end@implementation ViewController#pragma mark - lazy-(NSOutputStream *)outputStream{    if (!_outputStream) {        /*         第一个参数: 告诉系统数据流需要输出到哪个文件中         第二个参数: 如果传入YES, 代表每次都在上一次的后面追加         如果传入NO, 代表每次都从头开始         */        _outputStream = [NSOutputStream outputStreamToFileAtPath:self.path append:YES];        // 注意: 如果想利用输出流写入数据, 一定要打开数据流        // 如果数据流打开的文件不存在, 那么会自动创建个新的        [_outputStream open];    }    return _outputStream;}- (void)viewDidLoad {    [super viewDidLoad];    // 设置文件保存路径    self.path = [@"minion_01.mp4" cacheDir];    NSLog(@"%@",self.path);    [self largeDataDownLoad];}// 大文件下载-(void)largeDataDownLoad{    // 1.创建URL    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];    // 2.根据URL创建请求对象    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    // 2.1设置请求头从哪里开始下载    // 获取当前下载文件的大小    NSFileManager *manager =[NSFileManager defaultManager];    NSDictionary *dict = [manager attributesOfItemAtPath:self.path error:nil];    NSString *range = [NSString stringWithFormat:@"bytes:%zd-",[dict[NSFileSize] intValue]];    self.curDataLength = [dict[NSFileSize] intValue];    NSLog(@"%@",range);    [request setValue:range forHTTPHeaderField:@"Range"];    // 2.2发送请求    [NSURLConnection connectionWithRequest:request delegate:self];}#pragma mark - NSURLConnectionDataDelegate// 接收到服务器的响应后调用-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{    // 获取文件总大小    NSInteger totalDataSize = response.expectedContentLength + self.curDataLength;    self.totalDataSize = totalDataSize;}// 每次接收到服务器的数据后调用(会调用一次或多次)-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{    // 利用输出流往文件中写数据    [self.outputStream write:data.bytes maxLength:data.length];    // 获取当前已经下载的总大小    self.curDataLength += data.length;    // 更新进度条    self.progress.progress = 1.0 * self.curDataLength / self.totalDataSize;    NSLog(@"%f",self.progress.progress);}// 接收数据完成时调用-(void)connectionDidFinishLoading:(NSURLConnection *)connection{    NSLog(@"%s",__func__);    // 下载完成后关闭输出流和清空当前下载数据    [self.outputStream close];    self.curDataLength = 0;}// 接收数据失败调用-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{    NSLog(@"%s",__func__);}

5.文件上传

  • 文件上传需要设置请求头和请求体的格式,并且格式固定如下格式,一步都不能少,不然无法上传成功,后面会讲解AFN框架的时候,AFN已经将这些代码封装好了,我们也无需写这些复杂的代码段
// 1.设置请求头[request setValue:@"multipart/form-data; boundary=分割线" forHTTPHeaderField:@"Content-Type"];// 2.设置请求体非文件参数--分割线\r\nContent-Disposition: form-data; name="参数名"\r\n\r\n参数值\r\n文件参数--分割线\r\nContent-Disposition: form-data; name="参数名"; filename="文件名"\r\nContent-Type: 文件的MIMEType\r\n\r\n文件数据\r\n参数结束的标记--分割线--
  • 文件上传实例
#import "ViewController.h"// 请求头的分割线#define ZJHeaderBoundary @"----MrRight"// 请求体的分割线#define ZJBoundary [@"------MrRight" dataUsingEncoding:NSUTF8StringEncoding]// 结束符#define ZJEndBoundary [@"------MrRight--" dataUsingEncoding:NSUTF8StringEncoding]// 将字符串转换为二进制#define ZJEncode(str) [str dataUsingEncoding:NSUTF8StringEncoding]// 换行#define ZJNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]@interface ViewController ()@end@implementation ViewController-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{    // 1.创建URL    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];    // 2.根据URL创建请求对象    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    // 2.1设置请求头    request.HTTPMethod = @"POST";    NSString *temp = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", ZJHeaderBoundary];    [request setValue:temp forHTTPHeaderField:@"Content-Type"];    // 2.2设置请求体    NSMutableData *data = [NSMutableData data];    // 拼接请求体格式    // 2.2.1设置文件参数    [data appendData:ZJBoundary];    [data appendData:ZJNewLine];    // name : 对应服务端接收的字段类型(服务端参数的名称)    // filename: 告诉服务端当前的文件的文件名称(也就是告诉服务器用什么名称保存当前上传的文件)    [data appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"abc.png\"" dataUsingEncoding:NSUTF8StringEncoding]];    [data appendData:ZJNewLine];    // 注意点: 文件的类型不一样, 那么content-type的值也不一样    // 1.如果不知道数据时什么类型, 直接传application/octet-stream即可    // application/octet-stream是万能类型    // 数据的Content-Type类型我们称之为MIMETYPE    [data appendData:[@"Content-Type: application/octet-stream" dataUsingEncoding:NSUTF8StringEncoding]];    [data appendData:ZJNewLine];    [data appendData:ZJNewLine];    // 文件数据    UIImage *image = [UIImage imageNamed:@"abc"];    NSData *imageData = UIImagePNGRepresentation(image);    [data appendData:imageData];    [data appendData:ZJNewLine];    [data appendData:ZJNewLine];    // 2.2.2设置非文件参数    [data appendData:ZJBoundary];    [data appendData:ZJNewLine];    // name : 对应服务端接收的字段类型(服务端参数的名称)    [data appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];    [data appendData:ZJNewLine];    [data appendData:ZJNewLine];    [data appendData:[@"zj" dataUsingEncoding:NSUTF8StringEncoding]];    [data appendData:ZJNewLine];    // 2.2.3设置结束符号    [data appendData:ZJEndBoundary];    request.HTTPBody = data;    // 3.发送请求    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {        NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);    }];}

6.NSURLConnection和NSRunLoop

  • 通过代理监控NSURLConnection请求,以及请求过程设置在哪个线程执行
  • 通过alloc/init方法创建NSURLConnection,会自动发送异步网络请求
  • 只要利用NSURLConnection发送一个请求, 那么系统会就自动将NSURLConnection加入到当前线程的RunLoop中
  • 如果是在主线程中发送求情, 那么主线程的RunLoop永远存在, 所以NSURLConnection不会被释放,所以能够执行监听请求操作
  • 在主线程中发送请求
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];    NSURLRequest *request = [NSURLRequest requestWithURL:url];    // alloc/init方法会自动发送请求,默认发送异步请求(默认是在主线程中执行)    //NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];    // startImmediately:若为YES,则会马上发送异步请求,若为NO,需要手动启动发送请求    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];    // 发送请求    [conn start];    // 设置代理方法回调的线程(设置在子线程中调用)    [conn setDelegateQueue:[[NSOperationQueue alloc] init]];
  • 如果是在子线程中发送请求, 那么子线程默认没有RunLoop, 所以NSURLConnection会被释放,所以无法监听请求操作
  • 所以要想在子线程中发送的请求能够被监听,若是使用alloc/init方法,就必须自己创建RunLoop对象,并且在设置监听时运行RunLoop,这样NSYRLConnection就不会被马上释放,而是会等到RunLoop执行完毕后才会释放
  • 如果是通过alloc/init+startImmediately:方法调用,则不要要自己创建NSRunLoop对象,只需调用NSURLConnection的start方法,那么系统会将NSURLConnection添加到当前线程runloop的默认模式下,如果当前线程的runloop不存在, 那么系统内部会自动创建一个
  • 在子线程中发送请求
-(void)sendRequestInSubThread{    dispatch_async(dispatch_get_global_queue(0, 0), ^{    // 1.创建请求        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];        NSURLRequest *request = [NSURLRequest requestWithURL:url];        // alloc/init方法会自动发送请求,默认发送异步请求(默认是在主线程中执行)        // 2.发送请求        NSRunLoop *runloop = [NSRunLoop currentRunLoop];        NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];        [conn setDelegateQueue:[[NSOperationQueue alloc] init]];        [runloop run];        // 如果是在子线程中发送请求, 那么子线程默认没有RunLoop, 所以NSURLConnection会被释放        //        NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];        // 设置代理方法回调的线程(设置在子线程中调用)        //        [conn setDelegateQueue:[[NSOperationQueue alloc] init]];        // 如果调用NSURLConnection的start方法, 那么系统会将NSURLConnection添加到当前线程runloop的默认模式下, 如果当前线程的runloop不存在, 那么系统内部会自动创建一个        //        [conn start];    });}
0 0
原创粉丝点击