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进行文件下载的各种弊端和问题,以及解决方案。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容