iOS NSURLConnection

iOS 9.0之前 ,使用NSURLConnection比较多
iOS 7.0之后,苹果官方推出NSURLSession,并在9.0之后推荐使用且丢弃了NSURLConnection

因为NSURLSession在异步的处理上比NSURLConnection好很多

本文作为学习和了解,详解iOS9.0之前的NSURLConnection

NSURLConnection:
  • 从 iOS 2.0 开始,已经有10多年的历史了
    异步方法在 iOS 5.0 之后才有 ,
    在 iOS 5.0 之后,是通过代理的方式,来实现网络开发

本例使用NSURLConnection、NSFileHandle、NSOutputStream
实现了从本地服务器下载一个700M视频的demo

NSOutputStream示意图

对内存进行了优化,内存一直保持在30M之内
并用GCD+NSRunloop进行异步下载
不影响UI主线程

内存占用控制

并在后台打印出下载完成进度百分比


后台下载完成百分比
Demo详解:

#import "ViewController.h"

// Newsstand Kit‘s 杂志包,是专门做杂志的,主要在国外使用,因为国内的书籍盗版太严重
// ISBN书号,电子书也必须是唯一的
// NSURLConnectionDownloadDelegate 方法主要针对杂志的下载提供接口
// 如果在开发中,使用 NSURLConnectionDownloadDelegate 代理方法下载,能够监听下载进度,但是无法找到下载的文件
@interface ViewController () <NSURLConnectionDataDelegate>

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

//@property(nonatomic,strong) NSMutableData *fileData;        //用来每次接受到数据,拼接数据使用
@property(nonatomic,copy) NSString *tartgetFilePath;        //保存的目标路径

//@property(nonatomic,assign,getter=isFinished)BOOL finished;

@property(nonatomic,assign)CFRunLoopRef downloadRunloop;    //下载线程的运行循环
/*
 保存文件的输出流
 - (void)open;      写入之前,打开流
 - (void)close;     完成之后,关闭流
 */
@property(nonatomic,strong)NSOutputStream *fileStrem;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

/**
 NSURLConnection - 从 iOS 2.0 开始,已经有10多年的历史了
 异步方法在 iOS 5.0 之后才有 , 在 iOS 5.0 之后,是通过代理的方式,来实现网络开发
 那时是iOS网络开发的黑暗史,网络开发很难
 
 就因为这个黑暗史,才成就了一些第三方框架: AFN,ASI这些第三方框架
 
 NS - NextStep 公司的缩写
 
 - 开发简单的网络请求比较方便,可以采用异步方法
 - 开发发杂的网络请求,例如:大文件下载,仍然需要使用代理来开发,非常繁琐!
 
 **对NSURLConnection 的一些细节有所了解
 
 问题:1.没有下载进度,会影响用户体验
      2.有内存峰值,下载的文件有多大,NSData就会占用多大内存

 解决方法:
 - 通过代理的方式解决
 
 1.进度跟进,解决思路:
    1>在响应方法中获得文件总大小
    2>每次接受到数据,计算获得数据的总长度,和总大小相比,计算出百分比
 
 2.保存文件的思路
    1>保存完成写入磁盘
            测试结果:和异步方法执行一样,仍然存在内存峰值。
            推测:苹果的异步方法的实现思路,和刚才的delegate实现思路一样
            问题:下载的内存峰值依旧存在
    2>下载一个写一个
        1)NSFileHandle 彻底解决了内存峰值问题
        2)NSOutputStream 输出流
        - Socket 网络本质上,在客户端和服务器之间,数据的传递,都是以二进制流的方式传递的。
        - 对数据流的操作,一定要有一些网络底层的思想之后,才容易理解
        - 操作方式,比 FindHandle 更简洁
 */


-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //1.url
        NSString *urlString = @"http://127.0.0.1/1.mp4";
        urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlString];
        
        //2.request
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //3.connection
        //开始下载时的线程,是用 dispatch_async 创建的
        NSLog(@"开始 %@",[NSThread currentThread]);
        //    For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
        //为了保证连接的工作正常,调用线程的run loop 必须运行在默认的运行循环模式下
        NSURLConnection *conn =  [NSURLConnection connectionWithRequest:request delegate:self];//使用代理
        
        //设置代理工作的操作队列
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];//新的问题 默认还在主线程工作,干扰主线程UI更新
        
        //4.启动连接
        [conn start];
        
//        self.finished = NO;
        
        //5.启动运行循环
//        while (!self.isFinished) {
//            //启动一个死循环,每次监听0.1秒
//            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
//        }
        
        //CoreFoundation框架(纯C语言,是Foundation的基础)中,提供了对运行循环更底层的操作
        /*
         CFRunLoopStop(rl)    停止当前的runloop
         CFRunLoopGetCurrent()当前线程的runloop
         CFRunLoopRun();       直接运行当前线程的运行循环
         
         以下代码,是最标准的 runloop 的操作方法
         */
        //1.拿到当前线程的运行循环
        self.downloadRunloop = CFRunLoopGetCurrent();
        //2.启动运行循环
        CFRunLoopRun();
        
        NSLog(@"到我了吗?");
    });
    
//直接下载视频
//    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionErro){
//        [data writeToFile:@"/Users/Liu/Desktop/123.mp4" atomically:YES];
//        
//        NSLog(@"完成");
//        }];
    
}

#pragma mark - NSURLConnectionDataDelegate
//1.接收到服务器的响应 - 状态行&响应头 - 做一些准备工作
/*
 expectedContentLength 要下载文件的总大小 long long
 suggestedFilename     服务器建议保存的文件名
 */
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);
    
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
    
    //生成目标文件路径
    self.tartgetFilePath = [@"/Users/Liu/Desktop" stringByAppendingPathComponent:response.suggestedFilename];
    
    //删除文件,removeItemAtPath 如果文件存在直接删除,如果文件不存在,什么也不做
    [[NSFileManager defaultManager]removeItemAtPath:self.tartgetFilePath error:NULL];
    
    //以追加的方式打开文件流
    self.fileStrem = [[NSOutputStream alloc]initToFileAtPath:self.tartgetFilePath append:YES];
    [self.fileStrem open];
}



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

//2.接收到服务器的数据 - 此代理方法可能会执行多次 因为会收到多个data
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到数据长度 %tu",data.length);
    
    self.currentLength +=data.length;
    //计算百分比
    //progress = 小 long long / 大 long long
    float progress = (float)self.currentLength/self.expectedContentLength;
    
    NSLog(@"%f %@",progress,[NSThread currentThread]);
    
    //在主线程更新进度条
    dispatch_async(dispatch_get_main_queue(), ^{
        self.Progress.progress = progress;
    });
    
    //将数据追加到文件流中
    [self.fileStrem write:data.bytes maxLength:data.length];
    
//拼接数据
//    
//    [self.fileData appendData:data];
}

//-(void)writeToFileWithData:(NSData *)data{
//    // 文件操作
//    /*
//     NSFileManager :主要功能:创建目录,检查目录是否存在,遍历目录,删除文件。。。主要针对文件集的操作,类似于Finder
//     NSFileHandle: 文件”句柄“, 如果在开发中,看到 Handle 这个单词,就意味着是对前面的单词“File”进行操作的对象
//                    主要功能:对同一个文件进行二进制的读写操作的对象
//     */
//    
//    //注意:如果文件不存在,fp 在实例化的结果是空
//    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.tartgetFilePath];
//    
//    //判断文件是否存在 - 如果存在,则追加数据;如果不存在,直接将数据写入磁盘
//    if(fp == nil){
//        [data writeToFile:self.tartgetFilePath atomically:YES];
//    }else{
//        //如果存在,将data“追加”到现有文件
//        //1.将文件指针移动到文件的末尾
//        [fp seekToEndOfFile];
//        
//        //2.写入文件
//        [fp writeData:data];
//        
//        //3.关闭文件,在C语言的开发中,凡涉及到文件读写,打开和关闭通常是成对实现的
//        [fp closeFile];
//    }
//}

//3.所有数据加载完成 - 所有数据都传输完毕后调用,只是最后一个通知
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //结束时代理工作的线程,是指定 NSOperationQueue 调度的
    NSLog(@"完成 %@",[NSThread currentThread]);
    //把数据写入磁盘
//    [self.fileData writeToFile:self.tartgetFilePath atomically:YES];
//    //释放 fileData
//    self.fileData = nil;
    [self.fileStrem close];
    
//    //设置结束标记
//    self.finished = YES;
    
    //停止下载线程所在的运行循环
    CFRunLoopStop(self.downloadRunloop);
}

//4.下载失败或者错误,提示:在正常商业应用开发中一定要做出错误处理
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"失败 %@",error);
}
@end


PS:本文使用的下载源在本地服务器127.0.0.1,根据需要可以修改成其他下载源。

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

推荐阅读更多精彩内容