网络篇 - 03.NSURLConnection应用场景

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\n
Content-Disposition: form-data; name="参数名"
\r\n
\r\n
参数值
\r\n
文件参数
--分割线
\r\n
Content-Disposition: form-data; name="参数名"; filename="文件名"
\r\n
Content-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];
    });
}

你可能感兴趣的:(网络篇)