iOS网络编程(NSURLConnection)

原文:https://www.jianshu.com/p/78964aac72d5
虽然NSURLConnection已经被弃用,但是我们还是要了解NSURLConnection的用法,便于我们之后更好的理解NSURLSession。

1. NSURLConnection的使用

使用NSURLConnection发送请求的步骤很简单

  1. 创建一个NSURL对象,设置请求路径
    NSURL:请求地址
  2. 传入NSURL创建一个NSURLRequest对象,设置请求头和请求体
    NSURLRequest:一个NSURLRequest对象就代表一个请求,它包含的信息有
    一个NSURL对象、请求方法、请求头、请求体、请求超时等
    NSMutableURLRequest:NSURLRequest的子类,NSURLRequest默认的请求方法是GET,当我们需要修改请求方法时,请求头的时候就要用可变的NSMutableURLRequest
  3. 使用NSURLConnection发送请求
    NSURLConnection负责发送请求,建立客户端和服务器的连接,同时发送数据给服务器,并收集来自服务器的响应数据

2.NSURLConnection发送请求

1. 创建请求

- (NSURLRequest *)creatURLConnection
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
    //创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //修改请求方法:默认为GET, POST要大写
    request.HTTPMethod = @"POST";
    //设置请求体:请求体格式严格按照格式,参数之间用&隔开
    NSString *param = [NSString stringWithFormat:@"username=%@&pwd=%@",self.userName.text,self.pwd.text];
    request.HTTPBody = [param dataUsingEncoding:NSUTF8StringEncoding];
    //设置请求头:例如向服务器发送设备信息“User-Agent”,要严格按照请求内容规定请求
    NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
    [request setValue:systemVersion forHTTPHeaderField:@"User-Agent"];
    //设置请求超时时间
    [request setTimeoutInterval:10];
    
    return request;
}

2. 发送请求

NSURLConnection发送请求的方法分为同步和异步

注意:同步卡线程,异步不卡线程。异步有开启子线程的能力但不一定开启。

  • 同步
    同步采取将errorresponse地址传入方法作为参数,在方法内将返回值存入对应地址。
    核心方法:

    + (nullable NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse * _Nullable * _Nullable)response error:(NSError **)error
    

    实例代码:

    - (void)syncRequestWith:(NSURLRequest *)request
    {  
      NSURLResponse *response = nil;
      NSError *error = nil;
      NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
      NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
      NSLog(@"\ndata:%@ \nresponse:%@ \nerror:%@",[dict descriptionWithLocale:nil],response,error);
    }
    

    log:

    Printing description of response:
    2018-04-16 13:49:03.845628+0800 网络[897:31855] 
    data:{
        error = "用户名不存在";
    } 
    response: {  
     URL: http://120.25.226.186:32812/login } { Status Code: 200, Headers {
       "Content-Type" =     (
              "application/json;charset=UTF-8"
        );
        Date =     (
            "Mon, 16 Apr 2018 05:49:03 GMT"
        );
        Server =     (
            "Apache-Coyote/1.1"
        );
        "Transfer-Encoding" =     (
            Identity
        );
    } } 
    error:(null)
    

    总结:可以看到请求成功,response中存放响应头,data存放返回数据,error存放错误信息。

  • 异步
    异步请求根据对服务器返回数据的处理方式的不同,block回调和代理。
    核心方法:

    + (void)sendAsynchronousRequest:(NSURLRequest*) request
                            queue:(NSOperationQueue*) queue
                completionHandler:(void (^)(NSURLResponse* _Nullable response, NSData* _Nullable data, NSError* _Nullable connectionError)) handler
    

    实例代码:

    - (void)asyncRequestWith:(NSURLRequest *)request
    {
        //子线程请求
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
            if (connectionError) {
                NSLog(@"请求失败");
            }else {
                 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
                NSLog(@"\ndata:%@ \nreponse:%@ \nerror:%@",[dict descriptionWithLocale:nil],response,connectionError);
            }
            //回到主线程刷新UI
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                self.userName.text = @"请求完毕";
            }];
        }];
    }
    

    log同上。
    异步请求代理方法:

    //第一种 自动开启请求 类方法
    [NSURLConnection connectionWithRequest:request delegate:self];
    //第二种 自动开启请求 对象方法
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    //第三种 设置是否自动开启 如果不自动开启 手动开启
    NSURLConnection *manualConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
    [manualConnection start];
    

    代理方法:

    //1.当接收到服务器响应的时候调用,返回response响应头,我们可以在这里获取响应头的一些信息
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response
    {
        NSLog(@"-----didReceiveResponse-----");
    }
    
    //2.当接受到服务器返回的数据时调用,会被调用多次 data为服务器返回数据
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        NSLog(@"-----didReceiveData-----");
    }
    
    //3.请求结束后调用
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        NSLog(@"-----connectionDidFinishLoading-----");
    }
    
    //4.请求失败之后调用
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        NSLog(@"-----didFailWithError-----");
    }
    

    注意:
    1.苹果为方便我们刷新UI,默认代理方法在主线程,可以通过setDelegateQueue修改成子线程
    2.请求十分耗时,可以放在子线程中。initWithRequest会将方法会将NSURLConnection对象加入当前对应的RunLoop中,当我们在子线程中进行网络请求,默认子线程的RunLoop不会自动创建,NSURLConnection对象会被释放,因此我们需要开启子线程中的RunLoop,保证NSURLConnection对象不会被释放。另外,当在子线程中设置请求手动开启调用start方法,就不需要开启子线程RunLoop了,因为start方法内部如果发现RunLoop不存在就会自动创建。

3. NSURLConnection文件下载

小文件下载

当我们下载很小的文件的时候,例如一张很小的图片,不会占用太大内存的话我们可以通过URL直接进行下载

NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
self.imageView.image = [UIImage imageWithData:data];

也可以通过NSURLConnection进行下载

NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
    self.imageView.image = [UIImage imageWithData:data];
}];

较大文件下载

当我们需要下载一个较大文件的话,需要考虑的东西就很多了首先下载较大文件是一个耗时操作,我们应该肯定要通过什么方法来下载数据,第二,大文件需要时间较长,如果在下载过程中用户想要取消或者暂停应该怎么做,第三,下载文件较大应该怎么做存储,放在内存中?还是保存在沙盒中,都是我们需要考虑的。那么我们一个一个开始解决这些问题
第一:用什么方法请求数据?
因为文件较大,比较耗时,首先我们肯定要使用异步请求数据,另外同时在下载过程中我们同样需要拿到下载的数据,下载的进度,还要判断文件是否下载完成,因此使用异步下载代理方法。

#import "ViewController.h"
@interface ViewController ()
//下载的文件
@property (nonatomic, strong) NSMutableData *fileData;
//当前已经下载文件的大小
@property (nonatomic, assign) NSInteger currentLength;
//下载文件的总大小
@property (nonatomic, assign) NSInteger totalLength;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //1.确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
    //2.创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //3.设置代理,发送请求
    [NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark  NSURLConnectionDataDelegate  start
//1.接收到服务器响应的时候调用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    self.fileData = [NSMutableData data];
    //拿到文件的总大小
    self.totalLength = response.expectedContentLength;
    NSLog(@"%zd",self.totalLength);
}
//2.接收到服务器返回的数据,会调用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // 将下载的文件拼接到fileData中
    [self.fileData appendData:data];
    // 记录当前下载的多少
    self.currentLength = self.fileData.length;
    NSLog(@"%f",1.0 * self.currentLength / self.totalLength);
}
//3.当请求完成之后调用该方法
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    //保存下载的文件到沙盒
    NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    //拼接文件全路径
    NSString *fullPath = [caches stringByAppendingPathComponent:@"abc.mp4"];
    //写入数据到文件
    [self.fileData writeToFile:fullPath atomically:YES];
    NSLog(@"%@",fullPath);
}
// 4.当请求失败的适合调用该方法,如果失败那么error有值
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"didFailWithError");
}

至此我们已经已经实现了一个简单的文件下载,我们可以看到下载进度,也可以打印出沙盒存储目录找到下载的文件,但是还存在一些问题,第一,我们没有办法控制文件下载暂停。第二,我们发现开始下载后工程占用内存开始飙升,大约上升了我们下载的文件大小,这是因为fileData 这个属性在内存中也存储了一份我们下载的文件。
第一:暂停下载
当我们点击暂停的时候下载暂停,当点击开始的时候接着之前的下载,请求头中有属性可以设置要请求的内容,因此我们需要设置请求头,直接来看代码

// 断点下载需要设置请求头 因此request 要可变的 NSMutableURLRequest;
// 设置请求头
/*
 表示头500个字节:Range: bytes=0-499
 表示第二个500字节:Range: bytes=500-999
 表示最后500个字节:Range: bytes=-500
 表示500字节以后的范围:Range: bytes=500-
 */
// 传入已经下载文件的大小,表示从已经下载以后开始下载
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentLength];
NSLog(@"%@",range);
[request setValue:range forHTTPHeaderField:@"Range"];

其次我们需要设置后续下载内容拼接在之前下载好的内容之后,这需要用到文件句柄,在didReceiveData(接收到服务器返回数据的方法)中设置每次下载的数据拼接在已经下载好的数据之后。如果直接在didReceiveData方法中写入文件,会覆盖之前下载好的文件内容。

// 文件句柄
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
self.handle = handle;
//设置指向文件的末尾
[self.handle seekToEndOfFile];
// 写数据
[self.handle writeData:data];
// 也可以设置指定位置写入文件
//     [handle seekToFileOffset:(unsigned long long)];
NSFileManager *fileManager = [[NSFileManager alloc] init];
// 记录当前下载的多少
NSData *currentData = [fileManager contentsAtPath:self.filePath];
self.currentLength = currentData.length;

当然,文件句柄的创建我们可以写在didReceiveResponse接受到服务器响应的时候创建,然后用属性强引用,不必再每次返回数据的时候重新创建。文件句柄需要在connectionDidFinishLoading(请求完成之后)关闭并置空。

[self.handle closeFile];
self.handle = nil;

除了文件句柄,我们也可以使用输出流来写数据,达到和文件句柄一样的效果。

// 输出流
// 第一个参数:文件路径  第二个参数:是否拼接 YES表示往后拼接数据,NO表示覆盖
self.stream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];
// 输出流需要开启
[self.stream open];
// 输出流写数据
// 参数一:要写入的二进制数据,bytes类型 参数二:数据的大小
[self.stream write:data.bytes maxLength:data.length];
NSFileManager *fileManager = [[NSFileManager alloc] init];
// 记录当前下载的多少
NSData *currentData = [fileManager contentsAtPath:self.filePath];
self.currentLength = currentData.length;

输出流一样需要关闭。

//关闭输出流
[self.stream close];
self.stream = nil;

可以添加暂停随时取消下载,在需要的地方重新执行上面方法实现断点下载。
总结:
1. 通过设置请求头Range设置请求数据的范围
2. 通过响应头获取下载文件的一些基本信息,文件大小,名字等。
3. 使用文件句柄或者输出流来实现拼接文件

4.NSURLConnection 文件上传

文件上传步骤

  1. 确定请求路径
  2. 根据URL创建一个可变的请求对象
  3. 设置请求对象,修改请求方式为POST
  4. 设置请求头,告诉服务器我们将要上传文件(Content-Type)
  5. 设置请求体(在请求体中按照既定的格式拼接要上传的文件参数和非文件参数等数据)
    • 拼接文件参数
    • 拼接非文件参数
    • 添加结尾标记
  6. 使用NSURLConnection sendAsync发送异步请求上传文件
  7. 解析服务器返回的数据

你可能感兴趣的:(iOS网络编程(NSURLConnection))