NSURLConnection笔记

使用NSURLConnection下载文件

发送请求的步骤:

1.设置url

2.设置request

3.发送请求,同步or异步

使用同步方法下载文件

在主线程调用同步方法,一直在等待服务器返回数据,代码会卡住,如果服务器,没有返回数据,那么在主线程UI会卡住不能继续执行操作。有返回值NSData

//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection
NSLog(@"start");
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//一直在等待服务器返回数据
NSLog(@"--%d--",data.length);
  • 注意:同步的连接会阻塞调用它的线程,不一定是主线程

使用异步方法下载文件

没有返回值

    //1.url
    NSString *urlstr = @"xxxx";
    urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlstr];

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

    //3.connection
    NSLog(@"start");
    //使用这个方法会有内存峰值
    //queue参数是指定block的执行队列
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        //将文件写入磁盘
        //内存峰值:下载完成瞬间再把文件写入磁盘。下载文件有多大,NSData就会占用多大的内存。
        [data writeToFile:@"XXXXXX" atomically:YES];
        NSLog(@"finsh");
    }];
  • 开一条新的线程去发送请求,主线程继续往下走,当拿到服务器的返回数据的数据的时候再回调block,执行block代码段。不会卡住主线程。
  • 关于queue参数,队列的作用:An NSOperationQueue upon which the handler block will be dispatched.决定这个block操作放在哪个线程执行。

    如果是NSOperationQueue *queue=[[NSOperationQueue alloc]init];默认是异步执行。(接下来也会有提及)

    如果是主队列mainqueue,那么在子线程发送请求成功并获取到服务器的数据响应。就可以回到主线程中解析数据,并刷新UI界面。

  • 如果有刷新UI界面的操作应该放在主线程执行,不能放在子线程。

存在的问题:

1.没有下载进度,会影响用户体验

2.使用异步方法,下载完成执行回调时再把文件写入磁盘,会造成内存峰值的问题。下载文件有多大,NSData就会占用多大的内存

使用代理

问题1的解决办法:使用代理NSURLConnectionDataDelegate

1.在响应方法中获得文件总大小

2.每次接收到数据,计算数据的总长度,和总大小相比,得出百分比

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

    //1.url
    NSString *urlstr = @"xxxx";
    urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlstr];

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

    //3.connection,不做设置的话是在主线程中执行之后的下载
    NSLog(@"start");
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];

    //设置代理工作的操作队列
    [conn setDelegateQueue:[[NSOperationQueue alloc]init]];

    //4.启动连接
    [conn start];
//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);//response里面有状态行和响应头
    //记录文件总大小
    self.expectedContentLength = response.expectedContentLength;
    self.currentLength = 0;
}

//2.接收到服务器的数据 :此方法可能会执行多次,因为会接收到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到的数据长度%tu",data.length);
    //计算百分比
    self.currentLength += data.length;

    float progress = (float)self.currentLength / self.expectedContentLength;
    NSLog(@"%f",progress);
}

//3.所有数据加载完成 : 所有数据传输完毕之后被调用,是最后一个的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}

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

问题2的解决办法:

保存文件的思路:

a.拼接完成写入磁盘

b.下载一个写一个

1>NSFileHandle

2>NSOutputStream

a.拼接完成写入磁盘

1.生成目标文件路径

2.拼接数据

3.拼接完成写入磁盘

4.释放内存

//文件保存的路径
@property (nonatomic , strong) NSString *targetFilePath;
//用来每次接收到数据,拼接数据使用
@property (nonatomic , strong) NSMutableData *fileData;
//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);

    //生成目标文件路径
    self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];//下载后的文件名字不变
    //删除文件,如果文件存在,就会直接删除,如果文件不存在就什么也不做
    [[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];
}

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

//2.接收到服务器的数据 :次方法可能会执行多次,因为会接收到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    NSLog(@"接收到的数据长度%tu",data.length);

    //拼接数据
//     a.拼接完成写入磁盘
    [self.fileData appendData:data];
}

//3.所有数据加载完成 : 所有数据传输完毕之后被调用,是最后一个的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
     //a.拼接完成写入磁盘
    [self.fileData writeToFile:self.targetFilePath atomically:YES];
    //释放内存
    self.fileData = nil;//不释放的话,内存一直被占用,文件多大被占用的就有多大
}

存在的问题:测试结果:也是存在内存峰值

b.下载一个写一个

1>NSFileHandle

//2.接收到服务器的数据 :次方法可能会执行多次,因为会接收到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    //拼接数据
//    b.下载一个写一个
    [self writeToFileWithData:data];
}

- (void)writeToFileWithData:(NSData *)data{
    //文件操作
    /*
     NSFileManager:主要功能,创建目录,检查目录是否存在,遍历目录,删除文件...针对文件的操作,类似于Finder
     NSFileHandle:文件“句柄(文件指针)”,如果在开发中,看到Handle这个单词,就意味着是对前面的单词“File”进行操作的对象
                  主要功能,就是对同一个文件进行二进制的读写操作的对象。
     */

    //如果文件不存在,fp在实例化的结果是空
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.targetFilePath];
    //判断文件是否存在
    //如果不存在,直接将数据存入磁盘
    if(fp == nil){
        [data writeToFile:self.targetFilePath atomically:YES];
    }//如果存在,将Data追缴到现有文件
    else{
        //1.将文件指针移到文件的末尾
        [fp seekToEndOfFile];
        //2.写入文件
        [fp writeData:data];
        //3.关闭文件。在c语言开发中,凡是涉及到文件读写,打开和关闭通常是成对出现的
        [fp closeFile];
    }
}

测试结果:彻底解决了内存峰值的问题。是传统的文件操作方式。

2>NSOutputStream输出流

1.创建流

2.打开流

3.将数据追加到流

4.关闭流

//保存文件的输出流
/*
 - (void)open;写入之前打开流
 - (void)close;完成之后关闭流
 */
@property (nonatomic , strong) NSOutputStream *fileStream;
//代理
//1.接收到服务器的响应 :状态行和响应头,用来做一些准备工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    NSLog(@"%@",response);

    //生成目标文件路径
    self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];
    //删除文件,如果文件存在,就会直接删除,如果文件不存在没就什么也不做
    [[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:(NSData *)data{

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

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

NSURLConnection+NSRunLoop

新问题:下载默认下在主线程工作。下载本身是不是异步的(和NSURLSession不同)

[conn setDelegateQueue:[[NSOperationQueue alloc]init]];

指定了代理的工作队列之后,整个下载仍然会受主线程的干扰,以及更新ui(进度条)不及时。[在storyboard上添加了一个进度条以及一个uitextview,下载的时候,进度条会卡顿,滑动textview下载会暂停,停止滑动后又继续下载]

NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];在这里,delegate参数在api里面的说明如下:

The delegate object for the connection. The connection calls methods on this delegate as the load progresses. Delegate methods are called on the same thread that called this method. For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.为了保证连接的工作正常,调用线程的runloop必须运行在默认的运行循环模式下

一个异步网络请求的坑:关于NSURLConnection和NSRunLoopCommonModes这篇博文有提到这个问题:

如果是直接调用initWithRequest:delegate:startImmediately:(第三个参数用YES)或者方法initWithRequest:delegate:时(调用完connection就直接运行了),NSURLConnection会默认运行在NSDefaultRunLoopMode模式下,即使再使用scheduleInRunLoop:forMode:设置运行模式也没有用。如果NSURLConnection是运行在NSDefaultRunLoopMode,而当前线程是主线程(主线程有一个运行的runloop实例来支持NSURLConnection的异步执行),并且UI上有类似滚动这样的操作,那么主线程的Run Loop会运行在UITrackingRunLoopMode下,就无法响应NSURLConnnection的回调。此时需要首先使用initWithRequest:delegate:startImmediately:(第三个参数为NO)生成NSURLConnection,再重新设置NSURLConnection的运行模式为NSRunLoopCommonModes,那么UI操作和回调的执行都将是非阻塞的,因为NSRunLoopCommonModes是一组run loop mode的集合,默认情况下包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。

- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode在api中的说明
将connection实例回调加入到一个runloop,NSURLConnectionDelegate回调会在这个runloop中响应
Determines the run loop and mode that the connection uses to call methods on its delegate.
By default, a connection is scheduled on the current thread in the default mode when it is created. If you create a connection with the initWithRequest:delegate:startImmediately: method and provide NO for the startImmediately parameter, you can schedule the connection on a different run loop or mode before starting it with the start method. You can schedule a connection on multiple run loops and modes, or on the same run loop in multiple modes.
You cannot reschedule a connection after it has started.It is an error to schedule delegate method calls with both this method and the setDelegateQueue: method.
方法参数:
aRunLoop:调用代理方法时的NSRunLoop实例
mode:调用代理方法时的runloop模式
//运行在主线程下
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]
                initWithURL:self.URL
                cachePolicy:NSURLCacheStorageNotAllowed
                timeoutInterval:self.timeoutInterval];
self.connection =[[NSURLConnection alloc] initWithRequest:request
                                                     delegate:self
                                             startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];

1.为了不影响ui线程,把下载工作放到子线程里面

dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间
       //1.url
        NSString *urlstr = @"xxxx";
        urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlstr];

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

        //3.connection,不做设置的话是在主线程中执行之后的下载
        NSLog(@"start,%@",[NSThread currentThread]);
        NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];

        //设置代理工作的操作队列
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];

        //4.启动连接
        [conn start];
});

把网络操作方法放到子线程执行以后,回调代理方法无法执行,无法下载。

原因:主线程runloop会自动启动,子线程runloop默认不启动。将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件。[conn start];之后子线程被回收释放内存空间。

一个异步网络请求的坑:关于NSURLConnection和NSRunLoopCommonModes这篇博文有提到这个问题:如果是用GCD在其他线程中启动NSURLConnection:不会得到NSURLConnection回调,而从主线程中启动NSURLConnection可以得到回调,这是由于在GCD全局队列中执行时没有运行Run Loop,那么NSURLConnection也就无法触发回调了。

解决的办法如下:

2.启动子线程runloop

a.[[NSRunLoop currentRunLoop] run];使用这种方法使用,runloop永远释放不掉

b.开启一个循环,判断下载是否完成。这种方法对系统消耗非常大

@property (nonatomic , assign , getter = isFinished) BOOL finished;
    //主线程runloop会自动启动,子线程runloop默认不启动
    //将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件
    dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间
        ......

        //4.启动连接
        [conn start];

        self.finished = NO;
        //5.启动运行循环
// a.       [[NSRunLoop currentRunLoop] run];//这样写永远释放不掉
// b.
        while (!self.isFinished) {
            //启动一个死循环,每次监听0.1秒.
            //对系统消耗非常大
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
        }
    });

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
     ......
     self.finished = YES;
}

c.方法b的改进,例子里面使用到CFRunLoop

@property (nonatomic , assign) CFRunLoopRef rl;
    //主线程runloop会自动启动,子线程runloop默认不启动
    //将网络操作放在异步执行,异步的runloop不启动,没有办法监听到网络事件
    dispatch_async(dispatch_get_global_queue(0, 0), ^{//这样写,下载不执行了。[conn start];之后子线程被回收释放内存空间

        //1.url
        NSString *urlstr = @"xxxx";
        urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        NSURL *url = [NSURL URLWithString:urlstr];

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

        //3.connection,不做设置的话是在主线程中执行之后的下载
        //开始时的线程是由dispatch_async 创建的
        NSLog(@"start,%@",[NSThread currentThread]);
        NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];

        //设置代理工作的操作队列
        [conn setDelegateQueue:[[NSOperationQueue alloc]init]];
        //指定调度代理工作的操作队列。操作队列的特点:添加任务,立即异步执行,具体的线程程序员不能决定

        //4.启动连接
        [conn start];

        //5。启动运行循环
        //CF框架
//        CFRunLoopStop(CFRunLoopRef rl);停止指定的runloop
//        CFRunLoopGetCurrent();当前线程的runloop
//        CFRunLoopRun();直接运行当前线程的runloop
        //1.拿到当前的runloop
        self.rl = CFRunLoopGetCurrent();
        //2.启动运行循环
        CFRunLoopRun();
    });

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    //结束时代理工作的线程,是由指定的NSOperationQueue创建的,和创建下载操作的线程是不一样的。
    //关闭runloop,要关闭指定线程上的runloop。在这里拿到创建下载那个线程的runloop
    NSLog(@"finish,%@",[NSThread currentThread]);
    //关闭文件流
    ......
    CFRunLoopStop(self.rl);
}

- 开始时的下载线程是由dispatch_async 创建的,NSLog(@"start,%@",[NSThread currentThread]);

结束时代理方法工作的线程,是由指定的NSOperationQueue创建的:[conn setDelegateQueue:[[NSOperationQueue alloc]init]];,和创建下载操作的线程是不一样的。NSLog(@"finish,%@",[NSThread currentThread]);这里打印的两个线程结果是不一样的。

- [conn setDelegateQueue:[[NSOperationQueue alloc]init]];指定调度代理方法工作的操作队列。操作队列的特点:添加任务,立即异步执行,具体的线程程序员不能决定.

- 启动runloop:1.拿到当前的runloop self.rl = CFRunLoopGetCurrent();2.启动runloopCFRunLoopRun();

关闭runloop:要关闭指定线程上的runloop。在这里拿到创建下载那个线程的runloop CFRunLoopStop(self.rl);

时间: 2024-10-29 02:36:02

NSURLConnection笔记的相关文章

iOS学习笔记(八)——iOS网络通信http之NSURLConnection

转自:http://blog.csdn.net/xyz_lmn/article/details/8968182 移动互联网时代,网络通信已是手机终端必不可少的功能.我们的应用中也必不可少的使用了网络通信,增强客户端与服务器交互.这一篇提供了使用NSURLConnection实现http通信的方式. NSURLConnection提供了异步请求.同步请求两种通信方式. 1.异步请求 iOS5.0 SDK NSURLConnection类新增的sendAsynchronousRequest:queu

iOS学习笔记12-网络(一)NSURLConnection

一.网络请求 在网络开发中.须要了解一些经常使用的请求方法: GET请求:get是获取数据的意思,数据以明文在URL中传递,受限于URL长度,所以数据传输量比較小. POST请求:post是向server提交数据的意思.提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器url中查看到,大小没有限制. HEAD请求:请求头信息,并不返回请求数据体,而仅仅返回请求头信息,经常使用用于在文件下载中取得文件大小.类型等信息. 二.NSURLConnection NSURLConnection是

iOS学习笔记21-NSUrlSession与NSUrlConnection

首先 介绍一下HTTP协议 URL的全称是Uniform Resource Locator(统一资源定位符) URL中常见的协议有 HTTP超文本传输协议,访问的是远程的网络资源 FILE 访问的是本地计算机的资源 MAILTO 访问的是电子邮件地址 FTP 访问的是共享主机的文件资源 HTTP协议的作用是 1,规定了客户端和服务端之间的数据传输格式 2,让客户端和服务器能有效的进行数据沟通 为什么选择HTTP 1,简单快速 2,灵活 3,节省传输时间 HTTP的通信过程 1,请求:客户端向服务

【转】iOS学习笔记(八)——iOS网络通信http之NSURLConnection

移动互联网时代,网络通信已是手机终端必不可少的功能.我们的应用中也必不可少的使用了网络通信,增强客户端与服务器交互.这一篇提供了使用NSURLConnection实现http通信的方式. NSURLConnection提供了异步请求.同步请求两种通信方式. 1.异步请求 iOS5.0 SDK NSURLConnection类新增的sendAsynchronousRequest:queue:completionHandler:方法,从而使iOS5支持两种异步请求方式.我们先从新增类开始. 1)se

iOS 开发笔记-NSURLConnection的使用

通过NSURLConnection发送一个HTTP GET请求 //send a GET request to server with some params -(void)httpGetWithParams{ NSString *urlString = @"http://chaoyuan.sinaapp.com"; urlString = [urlString stringByAppendingString:@"?p=1059"]; NSURL *url = [N

NT_iOS笔记—NSURLConnection怎么把http改为https

一直使用NSURLConnection请求HTTP接口,现在为了安全性的考虑打算使用HTTPS. 那么怎么修改呢? 1.不需要证书验证 ps:我们使用的就是这种 1.1 直接修改HTTP为HTTPS; 1.2 确认有 "Security.framework" 1.3 修改完成,可以直接请求了. 2.需要证书验证 其他的和1是一样的,只不过需要加下面方法. - (void)connection:(NSURLConnection *)connection willSendRequestFo

SDWebImage源码学习笔记

//  这是我第二次学习sdwebimage源码,第一次学习吸收的很少,看不懂啊.第二次看个50%,在此记录一点笔记. 首先是目录: 1.SDWebImage目录 里面有两个类,SDWebImageCompat.h 里面有个根据屏幕设置图片scale的方法 SDWebImageOperation.h 声明了一个协议,取消操作 (可以理解这一个放的公共方法目录) 2.Downloader 目录(顾名思义,下载操作相关的目录)里面有两个关键的类 SDWebImageDownloaderOperati

iOS 8:【转】AFNetworking 学习笔记

源地址:http://fann.im/blog/2012/08/21/afnetworking-notes/ 这篇笔记是在 AFN v0.10.1 时候写的,AFN v1.0 以后加入了不少新东西,比如 SSL 支持,不过整体结构没有变化. 后续跟进了一篇 AFNetworking Notes 2 上图来自 @mattt 对 AFN 的介绍:Everybody Loves AFNetworking And So Can You!. 学习 AFN,简单记录一下以加深自己理解. AFN 的基础部分是

iOS 8:【转】AFNetworking 学习笔记二

源地址:http://fann.im/blog/2013/04/29/afnetworking-notes-2/ AFNetworking 学习笔记 的后续,记录一些 AFN 比较隐蔽的知识点. AFN 的设计过于理想化 AFN 的架构设计非常棒,使用起来也很简单,但一些设计过于理想化,在实际开发中会有一些条件不能满足,这时候 AFN 就会出现一些“坑”. 1. 缓存策略 NSURLRequest 默认的缓存策略是 NSURLRequestUseProtocolCachePolicy,网络请求是