iOS进阶_NSURLConnection(被弃用的原因:Connection的缺点)

2023-11-13

NSURLConnection下载

我们在前面的文章中介绍了MAC电脑搭建Apache服务器,在Sites文件夹中放置一个视频文件MVVM.mp4.pbb,通过NSURLConnection进行下载

访问http://localhost,我们可以进行查看,所有Apache服务器中的文件

这里写图片描述

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/MVVM.mp4.pbb";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    //connection
    NSLog(@"开始");
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        //将数据写入磁盘
        [data writeToFile:@"/Users/mac/Desktop/123.mp4.pbb" atomically:YES];
        NSLog(@"完成");
    }];
}

上面代码的写法有两个问题:

  • 1.没有下载进度,会影响用户体验
  • 2.内存偏高,有一个最大的峰值

假如使用第三方的文件下载工具,我们大家都已经轻车熟路了,但是我们就是要用NSURLConnection来做网络文件下载,应该怎么处理这两个问题呢?

NSURLConnection - 从iOS2.0开始有,已经有十多年的年龄了
NSURLConnection的异步加载 iOS5.0才有,在5.0以前通过代理来实现网络开发(这个时间开发者异常难熬

- 开发简单的网络请求还是比较方便的,直接用异步方法
- 开发复杂的网络请求,比如大文件的网络下载,步骤非常繁琐
在平时开发中,我们几乎不会用到NSURLConnection,因为我们用的都是别人给你封装好的,但是我们也要去了解挖掘它,才能更好的使用三方库。

NSURLConnection进度监听

问题:
1.没有下载进度,会影响用户体验
2.内存偏高,有一个最大的峰值

解决办法:
- 通过代理方式来解决
1.进度跟进
- 在响应方法中获得文件总大小
- 每次接收到数据,计算数据的总比例

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDelegate>
/** 要下载文件的总大小 **/
@property(nonatomic,assign)long long expectedContentLength;
/** 当前下载的长度 **/
@property(nonatomic,assign)long long currentLength;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/MVVM.mp4.pbb";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    NSLog(@"开始");
    //NSURLConnection的构造方法
    NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
    //启动链接
    [con start];

}

#pragma mark - <NSURLConnectionDelegate>
//1.接受到服务器的响应 - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename 服务器建议保存的文件名称
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f",progress);
}

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}
@end

拼接数据写入文件

问题:
1.没有下载进度,会影响用户体验
2.内存偏高,有一个最大的峰值

解决第二个问题的办法,我们猜测是因为一次性写入才使内存偏高 我们有两种方法一次进行校验:

2.保存文件的思路

有两种解决方式
1.保存完成一次性写入磁盘
2.边下载边写入

保存完成一次性写入磁盘

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDelegate>
/** 要下载文件的总大小 **/
@property(nonatomic,assign)long long expectedContentLength;
/** 当前下载的长度 **/
@property(nonatomic,assign)long long currentLength;
/** 保存目标 **/
@property(nonatomic,copy)NSString * targetFilePath;
/** 用来每次接收到的数据,拼接  **/
@property(nonatomic,strong)NSMutableData * fileData;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/MVVM.mp4.pbb";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    NSLog(@"开始");
    //NSURLConnection的构造方法
    NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
    //启动链接
    [con start];

}

#pragma mark - <NSURLConnectionDelegate>
//1.接受到服务器的响应 - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename 服务器建议保存的文件名称
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    //生成目标文件的路径
    self.targetFilePath = [@"/Users/mac/Desktop/" stringByAppendingPathComponent:response.suggestedFilename];
}

-(NSMutableData *)fileData{
    if (!_fileData) {
        _fileData = [[NSMutableData alloc]init];
    }
    return _fileData;
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f",progress);

    //拼接数据
    [self.fileData appendData:data];
}

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //将数据写入磁盘
    [self.fileData writeToFile:self.targetFilePath atomically:YES];
}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}
@end

这里写图片描述

使用第一种方式,文件保存完成写入磁盘,我们看见峰值升到很高,且都不会掉下来了,原因是@property(nonatomic,strong)NSMutableData * fileData; 是用strong来修饰的,且数据写入磁盘后,fileData没有被释放。因此要手动释放。self.fileData = nil;

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //将数据写入磁盘
    [self.fileData writeToFile:self.targetFilePath atomically:YES];
    //释放fileData
    self.fileData = nil;
}

这里写图片描述

此时再看,内存依然很大,问题没有被解决。我们可以得出结论,保存完成后一次性写入磁盘和异步方法执行的效果是一样的,仍然存在内存问题
此时我们是不是就可以推测:苹果的异步方法的实现思路就是此时我们在上面做的实现思路呢?

NSFileHandle写入

NSFileManager:主要功能,创建目录,检查目录是否存在,遍历目录,删除文件..针对文件操作Finder
NSFileHandle:文件“句柄”,Handle意味着是对前面单词的“File” 操作或者处理
主要功能:就是对同一个文件进行二进制的读和写

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDelegate>
/** 要下载文件的总大小 **/
@property(nonatomic,assign)long long expectedContentLength;
/** 当前下载的长度 **/
@property(nonatomic,assign)long long currentLength;
/** 保存目标 **/
@property(nonatomic,copy)NSString * targetFilePath;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/开班典礼介绍2017-11-27_220225.wmv";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    NSLog(@"开始");
    //NSURLConnection的构造方法
    NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
    //启动链接
    [con start];

}

#pragma mark - <NSURLConnectionDelegate>
//1.接受到服务器的响应 - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename 服务器建议保存的文件名称
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    //生成目标文件的路径
    self.targetFilePath = [@"/Users/mac/Desktop/" stringByAppendingPathComponent:response.suggestedFilename];
    //删除 -- removeItemAtPath 如果文件存在,就会直接删除,如果文件不存在,就什么也不做,也不会报错
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f",progress);

    //拼接数据
    [self writeToFileWithData:data];
}
-(void)writeToFileWithData:(NSData *)data{

    //注意:fp 中的 p 是指指针 文件句柄也可以理解为文件指针
    //如果文件不存在,fp 在实例化的结果是空
    NSFileHandle * fp = [NSFileHandle fileHandleForWritingAtPath:self.targetFilePath];
    //判断文件是否存在,直接将数据写入磁盘
    if (fp == nil) {
        [data writeToFile:self.targetFilePath atomically:YES];
    }else{
        //如果存在
        //1.将文件指针移到文件的末尾
        [fp seekToEndOfFile];
        //2.写入文件
        [fp writeData:data];
        //3.关闭文件 在c语音的开发中,泛是涉及到文件读写,都有打开和关闭的操作
        [fp closeFile];
    }

}
//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //将数据写入磁盘

}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}
@end

NSFileHandle 彻底解决了内存峰值的问题

这里写图片描述

但是有没有一种更加简单的方式呢?我们可以使用NSOutputStream输出流的方式来操作。

NSOutputStream拼接文件

NSOutputStream 输出流

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDelegate>
/** 要下载文件的总大小 **/
@property(nonatomic,assign)long long expectedContentLength;
/** 当前下载的长度 **/
@property(nonatomic,assign)long long currentLength;
/** 保存目标 **/
@property(nonatomic,copy)NSString * targetFilePath;
/** 保存文件的输出流
 - (void)open; 写入之前打开流
 - (void)close; 完成之后关闭流
 **/
@property(nonatomic,strong)NSOutputStream * fileStream;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/开班典礼介绍2017-11-27_220225.wmv";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    NSLog(@"开始");
    //NSURLConnection的构造方法
    NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
    //启动链接
    [con start];

}

#pragma mark - <NSURLConnectionDelegate>
//1.接受到服务器的响应 - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename 服务器建议保存的文件名称
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    //生成目标文件的路径
    self.targetFilePath = [@"/Users/mac/Desktop/" stringByAppendingPathComponent:response.suggestedFilename];
    //删除(用户第二次点击下载) -- removeItemAtPath 如果文件存在,就会直接删除,如果文件不存在,就什么也不做,也不会报错
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];

    //输出流创建 - 以追加的方式开发文件流
    self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.targetFilePath append:YES];
    [self.fileStream open];
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f",progress);

    //将数据追加到文件流中
    [self.fileStream write:data.bytes maxLength:data.length];
}

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //关闭文件流
    [self.fileStream close];
}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}
@end

我们看,使用文件输出流的方式是不是比使用文件句柄的方式更加简洁呢?

Connection在多线程下的问题

以上执行的下载文件的代码执行是在哪个线程中呢?答案是在主线程中,因为我们在所有的代码中没有涉及到任何多线程的东西。我们肯定要把这些操作放到子线程中,防止阻塞主线程。我们使用setDelegateQueue就可以了

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSString * urlStr = @"http://127.0.0.1/开班典礼介绍2017-11-27_220225.wmv";
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL * url =[NSURL URLWithString:urlStr];

    //request
    NSURLRequest * request = [NSURLRequest requestWithURL:url];

    NSLog(@"开始");
    //NSURLConnection的构造方法
    NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
    //设置代理工作的操作队列
    [con setDelegateQueue:[[NSOperationQueue alloc]init]];
    //启动链接
    [con start];
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f %@",progress,[NSThread currentThread]);

    //将数据追加到文件流中
    [self.fileStream write:data.bytes maxLength:data.length];
}

接下来,我们在Main.storyboard中添加两个控件,并把progressView添加关联到代码,在主线程中更新progressView的显示?我们这么做合理嘛?一切看上去很合理,但是注意!!这是一个很大的坑!!

这里写图片描述

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f %@",progress,[NSThread currentThread]);
    //在主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
         self.progressView.progress = progress; //直接影响UI
    });

    //将数据追加到文件流中
    [self.fileStream write:data.bytes maxLength:data.length];
}

运行代码,点击下载,拖动TextFilder,我们就会发现,操作UI的时候,文件是不会下载的,放开拖动后,文件下载才会重新被执行。它仍然阻塞主线程!很奇怪,我们明明把文件下载放到了子线程中,为什么还能阻塞主线程呢?

新的问题:默认Connection是在主线程工作,指定了代理的工作队列之后,整个下载仍然是在主线程,UI事件能够卡主下载

NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];

这个代码为了为了保证连接的正常,调用的这个线程的Run loop 必须在默认的Runloop模式下!!这是官方给出的说明。

我们知道NSURLConnection 在iOS8.0以后将会被弃用,它有很多的不足。这就是它的不足之处!!我们开发者为什么不用NSURLConnection,这就是原因。

Connection+ Runloop

根据上面我们抛出的问题,那我们想,能不能把文件下载的所有操作放到子线程中,能不能解决问题呢?

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSString * urlStr = @"http://127.0.0.1/开班典礼介绍2017-11-27_220225.wmv";
        urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL * url =[NSURL URLWithString:urlStr];

        //request
        NSURLRequest * request = [NSURLRequest requestWithURL:url];

        NSLog(@"开始");
        //NSURLConnection的构造方法
        NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
        //设置代理工作的操作队列
        [con setDelegateQueue:[[NSOperationQueue alloc]init]];
        //启动链接
        [con start];

        NSLog(@"来了!");
    });

}

答案是不能!! 文件下载的代理方法直接就不走了!!

这里写图片描述

原因是RunLoop是负责监听事件:触摸,时钟,网络等的,在主线程中创建NSURLConnection,RunLoop 可以被启动。在子线程中RunLoop是默认不启动的,此时的网络事件是不监听的!!

执行完NSLog(@"来了!");之后,子线程就被回收了![con start];是没有意义的。

所以,我们要启动运行循环

//启动链接
 [con start];

//5.启动运行循环
[[NSRunLoop currentRunLoop] run];
NSLog(@"来了!");

注意,一般我们启动RunLoop的时候不要使用run,使用run来启动一旦启动,就没办法被回收了。此时我们仅仅用来演示用。

此时,我们算是解决了这个问题。但是这个子线程是没办法被回收的,所以不能用run,可以需要手动的方式来使runloop启动起来。当然这种方式比较令人不爽。。。

我们定义一个标记属性

/** 标记  **/
@property(nonatomic,assign,getter=isFinished)BOOL * finished;
self.finished = NO;

//5.启动运行循环
while (!self.isFinished) {
            //启动一次Runlloop循环,监听事件
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
 }
//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕");
    //关闭文件流
    [self.fileStream close];

    //设置结束标记
    self.finished = YES;
}

当然,这种方式对系统消耗很大,我们更优的解决方式是使用CFRunloop

全部代码如下

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

/** 要下载文件的总大小 **/
@property(nonatomic,assign)long long expectedContentLength;
/** 当前下载的长度 **/
@property(nonatomic,assign)long long currentLength;

/** 下载线程的运行循环  **/
@property(nonatomic,assign)CFRunLoopRef downloadRunloop;

/** 保存目标 **/
@property(nonatomic,copy)NSString * targetFilePath;

@property(nonatomic,strong)NSOutputStream * fileStream;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}


/**
 问题:
 1.没有下载进度,会影响用户体验
 2.内存偏高,有一个最大的峰值

 解决办法:
  - 通过代理方式来解决
  1.进度跟进
   - 在响应方法中获得文件总大小
   - 每次接收到数据,计算数据的总比例
  2.保存文件的思路
   - 因为一次性写入才使内存偏高
   有两种解决方式
   1.保存完成写入磁盘
   2.边下载边写入
    1.NSFileHandle 彻底解决了内存峰值的问题
    2.NSOutputStream 输出流

 新的问题:默认Connection是在主线程工作,指定了代理的工作队列之后,整个下载仍然是在主线程,UI事件能够卡主下载
 */
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSString * urlStr = @"http://127.0.0.1/开班典礼介绍2017-11-27_220225.wmv";
        urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL * url =[NSURL URLWithString:urlStr];

        //request
        NSURLRequest * request = [NSURLRequest requestWithURL:url];

        NSLog(@"开始");
        //NSURLConnection的构造方法
        NSURLConnection * con = [NSURLConnection connectionWithRequest:request delegate:self];
        //设置代理工作的操作队列
        [con setDelegateQueue:[[NSOperationQueue alloc]init]];
        //启动链接
        [con start];

        //5.启动运行循环
        //coreFoundation框架 CFRunloop
        /*
         CFRunloopStop() 停止指定的runloop
         CFRunLoopGetCurrent() 拿到当前的runloop
         CFRunLoopRun() 直接启动当前的运行循环
         */
        //1.拿到当前线程的运行循环
        self.downloadRunloop = CFRunLoopGetCurrent();
        //2.启动运行循环
        CFRunLoopRun();
        NSLog(@"来了!");
    });

}

#pragma mark - <NSURLConnectionDelegate>
//1.接受到服务器的响应 - 状态行&响应头 - 做一些准备工作
//expectedContentLength  需要下载文件的总大小 long long
//suggestedFilename 服务器建议保存的文件名称
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    NSLog(@"%@",response);
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    //生成目标文件的路径
    self.targetFilePath = [@"/Users/mac/Desktop/" stringByAppendingPathComponent:response.suggestedFilename];
    //删除(用户第二次点击下载) -- removeItemAtPath 如果文件存在,就会直接删除,如果文件不存在,就什么也不做,也不会报错
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];

    //输出流创建 - 以追加的方式开发文件流
    self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.targetFilePath append:YES];
    [self.fileStream open];
}

//2.接受到服务器的数据 - 此代理方法可能会执行很多次 因为拿到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    self.currentLength += data.length;
    //计算百分比
    //progress = long long / long long
    float progress =  (float)self.currentLength / self.expectedContentLength;
    NSLog(@"接受数据进度%f %@",progress,[NSThread currentThread]);
    //在主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
         self.progressView.progress = progress; //直接影响UI
    });

    //将数据追加到文件流中
    [self.fileStream write:data.bytes maxLength:data.length];
}

//3.所有的数据加载完毕 - 所有数据都传输完毕,只是一个最后的通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕 %@",[NSThread currentThread]);
    //关闭文件流
    [self.fileStream close];

    //停止下载线程所在的运行循环
    CFRunLoopStop(self.downloadRunloop);
}

//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{

}
@end
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS进阶_NSURLConnection(被弃用的原因:Connection的缺点) 的相关文章

随机推荐

  • rails的一点点认识

    1 会开创新项目 rails new 名称 2 会创建控制类 rails g generate controller 类名 html文件名 3 会创建model rails g generate model 名称 散列属性key value
  • ipsec openswan资料搜集

    http blog csdn net rosetta article category 1190390 http blog chinaunix net uid 127037 list 1 html sid 136058 http blog
  • 大数据面试题(五)----HIVE面试题

    版权声明 本文为CSDN博主 北京小辉 的原创文章 遵循 CC 4 0 BY SA 版权协议 转载请附上原文出处链接及本声明 原文链接 https blog csdn net silentwolfyh article details 103
  • 在进入微信小程序之前显示登录页面的解决方案

    这几天想要在小程序进入之前做个登陆的页面 用户输入账号密码正确才能进入小程序 但是看了因为小程序一启动就是默认进入tabar里的第一个页面 上了百度查也是没有哦好的方式 自己琢磨了以下的几种方式 1 使用路由重定向 小程序中有几个用于重新向
  • Spring整合mybatis完整maven配置

    Spring整合mybatis完整maven配置 Maven配置 2022年1月23 基本都是最新版本
  • HTML 表单标签

    一 什么是表单标签 表单是用来采集用户的输入数据 然后将数据提交给服务器 二 表单的组成 一个表单有三个基本组成部分 表单标签 这里面包含了处理表单数据所用程序的URL以及数据提交到服务器的方法 表单域 包含了文本框 密码框 隐藏域 多行文
  • #1295-D: Deprecated declaration I2C_Start - give arg types

    KEIL5编译器报警告 警告内容如下 1295 D Deprecated declaration I2C Start give arg types 警告原因无参数函数的函数声明没有标明参数为void型 在函数声明中加入void即可 警告代码
  • 1.Kubernetes基础入门学习概述系统架构及组件浅析

    目录 0x00 基础简述 1 发展经历 2 简要介绍 3 系统架构 Borg 系统 Kubernetes 系统 0x01 组件浅析 1 Kubernetes Master 2 Kubernetes Node 3 Kubernetes 插件
  • Keras利用卷积神经网络(CNN)识别手写数字(环境python3.5)

    今天介绍如何利用卷积神经网络进行MNIST数据集的手写数字识别 即将手写数字图像images识别为数字标签labels 目录 数据预处理 模型建立 评估模型准确率 进行预测 建模思路如下图 数据预处理 MNIST数据集共有训练数据60 00
  • json to dart工具

    工具链接https javiercbk github io json to dart
  • 单片机开发---ESP32S3移植lvgl+触摸屏

    书接上文 单片机开发 ESP32 S3模块上手 本章内容 熟悉一下ESP32S3的开发 修改范例程序的lvgl 使之能够匹配现在的显示屏 具体工作大概为通过SPI接口连接一块SPI串口屏幕 并且适配lvgl 最后加上触摸屏作为输入 屏幕 用
  • 判断是pc端还是移动端

    Navigator对象 Navigator 对象包含有关浏览器的信息 下面的userAgent 属性是一个只读的字符串 声明了浏览器用于 HTTP 请求的用户代理头的值 所以我们可以通过判断navigator useragent里面是否有某
  • 微信小程序-拒绝了语音权限后,重新授权

    效果图 第一步 封装在utils里 哪个页面需要的话 直接调用 export function impower var t this wx authorize scope scope record success res fail wx s
  • 递归解决八皇后回溯算法

    问题简述 八皇后问题是一个古来而著名的问题 该问题是19世纪著名的数学家高斯同学提出来的 在8 8的国际象棋上摆放八个皇后 使其不能互相的攻击 也就是说 任意的两个皇后不能放在同一行或则是同一个列或者是同一个对角线上 问有多少个摆放的方法
  • Skywalking-9.6.0系列之本地源码编译并启动

    Skywalking相信有很多人使用过 通过容器或者下载安装包进行安装的 今天从源代码角度 拉取 构建 启动 官方文档步骤简洁明了 我这边会结合自己遇到的一些问题做出总结 当前构建资源版本 MAC 10 15 7 IDEA 2021 2 n
  • 正版steam gta5如何免登录教程

    gta5中文叫做侠盗猎车手5 是一款围绕犯罪为主题的开放式动作冒险游戏 凭借其全新的雷霆引擎 不一样的战斗方式 乃至今日仍受到众多玩家的喜爱 这么受欢迎的游戏当然需要付费购买啦 这可让许多小伙伴们都望而止步 为了满足玩家们的游戏需求 小编此
  • 常用的加解密技术(2)

    公开密钥体系 公开密钥密码体制是现代密码学的最重要的发明和进展 一般理解密码学 Cryptography 就是保护信息传递的机密性 但这仅仅是当今密码学主题的一个方面 对信息发送与接收人的真实身份的验证 对所发出 接收信息在事后的不可抵赖以
  • python切片的总结

    目录 介绍切片 格式 1 切割单个值 2 切割完整对象 3 start和end全部取正数的情况 4 start和end全部取负数的情况 5 start和end正负混合情况 7 切片中的三个参数为表达式 8 切片可以操作其他的对象 总结 介绍
  • Centos7 防火墙详细操作

    centos7 通过firewall cmd命令添加防火墙白名单 1 查看防护墙状态 firewall cmd state 或 systemctl status firewalld active running gt 表示防火墙已经开启 i
  • iOS进阶_NSURLConnection(被弃用的原因:Connection的缺点)

    NSURLConnection下载 我们在前面的文章中介绍了MAC电脑搭建Apache服务器 在Sites文件夹中放置一个视频文件MVVM mp4 pbb 通过NSURLConnection进行下载 访问http localhost 我们可