iOS网络—NSURLConnection文件下载 -- 弊端

一、开启Mac本地Apache服务器

开启Mac自带Apache服务器

二、NNSURLConnection文件下载

2.1、方式一:sendAsynchronousRequest + Block

    //1、url
    NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
    //1.1、url有中文,需要转码
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url  = [NSURL URLWithString: urlStr];
    
    //2、request
    NSURLRequest *request  = [NSURLRequest requestWithURL:url];
    
    //3、connection
    NSLog(@"开始");
    
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        //将数据写入到磁盘
        [data writeToFile:@"/Users/liuyi/Desktop/123/123.wmv" atomically:YES];
        NSLog(@"完成!");
    }];

问题:

1.没有下载进度、会影响用户体验
     解决方法:
         --通过代理方式来解决
         1.进度跟进
           --在响应头方法中获取文件的总大小
           --每次接收数据的,计算数据的比例
2.内存偏高,会有最大的峰值!
     保存文件的思路
        - 保存完成写入磁盘
            测试结果:和异步方法执行的效果一样,仍然存在内存问题。
            推测:苹果的异步方法的实现思路,就我们刚刚写的代码
 
        - 边下载边写
           1.NSFileHandle 彻底解决了内存峰值的问题。
           2.NSOutputStream 输出流

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

2.2、方式二:避开Block,显示加载进度 -- Delegate

进度跟进
--在响应头方法中获取文件的总大小
--每次接收数据的,计算数据的比例

    //1.url
    NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
    //1.1.url有中文,需要转码
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url  = [NSURL URLWithString: urlStr];
    
    //2.request
    NSURLRequest *request  = [NSURLRequest requestWithURL:url];
    
    //3.connection + delegate
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    
    //启动连接
    [conn start];

注意:
NSURLConnectionDownloadDelegate:千万不要用!专门针对杂志下载提供的接口
如果NSURLConnectionDownloadDelegate下载,能够监听到下载进度。没有办法找到下载文件
Newsstand Kit's专门用来做杂志的Kit


#pragma mark -- NSURLConnectionDeleagate
//1.接收服务器的响应 ---状态行和响应头--做一些准备工作
// expectedContentLength 文件的总大小
// suggestedFilename  建议保存的名字
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"%@",response);
    
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    
    self.currentlength = 0;
    
    //生成目标文件路径
    self.tartgetFilePath = [@"/Users/linxiang/Desktop/123" stringByAppendingPathComponent:response.suggestedFilename];
}

// 懒加载 - 初始化
-(NSMutableData *)fileData
{
    if (!_fileData) {
        _fileData = [[NSMutableData alloc]init];
    }
    return  _fileData;
}

//2.接收服务器的数据 -- 此代理方法会被执行多次! 因此我们会拿到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //NSLog(@"接收数据的长度 %tu",data.length);
    self.currentlength += data.length;
    
    //计算百分比
    //progress = (float)long long / long long
    float progress = (float)self.currentlength / self.expectedContentLength;
    
    NSLog(@"%f",progress);
    
    //拼接data
    [self.fileData appendData:data];
}

//3.所有数据加载完毕--所有数据加载完毕,会一个通知!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕!");
    
    //数据吸写入磁盘
    [self.fileData writeToFile:self.tartgetFilePath atomically:YES];
}


//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"异常!");
}

问题:

1.内存偏高,会有最大的峰值!
     保存文件的思路
        - 保存完成写入磁盘
            测试结果:和异步方法执行的效果一样,仍然存在内存问题。
            推测:苹果的异步方法的实现思路,就我们刚刚写的代码
 
        - 边下载边写
           1.NSFileHandle 彻底解决了内存峰值的问题。

2.3、方式三:为了解决内存峰值 -- 边下载边写入 NSFileHandle

NSFileManager: 主要功能,创建目录、检查目录是否存在,遍历目录、删除文件、拷贝文件、针对文件的操作;
NSFileHandle: 文件“句柄”,对文件的操作! 主要功能:就同一个文件进行二进制读写;

-(void)writeFileData:(NSData *)data
{
/*
    如何判断文件是否下载完成!
      1.判断进度?判断完成通知?
      2.判断时间、判断大小?
      3.MD5
     
    最合理的方案:
      1.服务器会对你的下载文件 计算好一个MD5 将此MD5 传给客户端;
      2.开始下载文件。。。。。
      3.下载完成时,对下载的文件做一次MD5 
      4.比较服务器返回的MD5 和你 自己计算的MD5 比较 == 下载完成!
 */
    
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.tartgetFilePath];
    
    //如果文件不存在,直接将数据写入磁盘
    if (fp == nil) {
        [data writeToFile:self.tartgetFilePath atomically:YES];
    }else
    {
        //如果存在,将data追加到现在文件的末尾
        [fp seekToEndOfFile];
        //写入文件
        [fp writeData:data];
        //关闭文件
        [fp closeFile];
    }
}

2.4:方式四:为了解决内存峰值 -- 边下载边写入 NSOutputStream

//1、创建输出流
    self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.tartgetFilePath append:YES];
    [self.fileStream open];

//2、判断是否有空间可写
    if ([self.fileStream  hasSpaceAvailable]) {
     //2.2、写入数据
     [self.fileStream write:data.bytes maxLength:data.length];
    }

//3、关闭文件流
    [self.fileStream close];


新的问题:

默认的connection 是在主线程工作,指定了代理的工作的队列之后,整个下载还是在主线程 。UI事件能够卡住下载


2.5、方式五:为了解决 " connection下载在主线程,从而卡住页面,或者UI卡住下载 " 的情况 -- RunLoop

1、将NSConnection请求放入 子线程 中,这样就行了吗??
------答案是否定的:由于子线程的RunLoop默认不开启,导致子线程执行完成就关闭了,根本没有时间进行下载的操作;
2、所以需要我们手动开启子线程的RunLoop
3、下载完成,需要我们关闭子线程的RunLoop,让子线程退出;

//将网络操作放在异步线程,异步的运行循环默认不启动,没有办法监听接下来的网络事件
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        //1.url
        NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url  = [NSURL URLWithString: urlStr];
        
        //2.request
        NSURLRequest *request  = [NSURLRequest requestWithURL:url];
        
        //3.connection
        NSLog(@"开始");
        /*
         For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
         为了保证链接正常工作,调用线程的runloop 必须运行在默认的运行循环模式下
         */
        NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
        
        //设置代理工作时操作
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
        
        //启动连接
        [conn start];
        
        //5.启动运行循环 - 让子线程不死
        /*
         CoreFoundation 框架 CFRunLoop
         CFRunloopStop() 停止指定的runloop
         CFRunloopGetCurrent() 获取当前的Runloop
         CFRunloopRun() 直接启动当前的运行循环
         */
        
        //1、拿到当前的运行循环
        self.downloadRunloop = CFRunLoopGetCurrent();
        
        //2.启动当前的运行循环
        CFRunLoopRun();
        
        NSLog(@"来了");
    });

下载完成后,手动停止下载线程所在的RunLoop

//3.所有数据加载完毕--所有数据加载完毕,会一个通知!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"完毕!%@",[NSThread currentThread]);

    //停止下载线程所在的runloop
    CFRunLoopStop(self.downloadRunloop);
}

至此基本诠释了NSConnection进行文件下载的各种弊端和问题,以及解决方案。

你可能感兴趣的:(iOS网络—NSURLConnection文件下载 -- 弊端)