iOS网络基础——NSURLConnection使用详解举例

一、整体介绍

NSURLConnection是苹果提供的原生网络访问类,但是苹果很快会将其废弃,且由NSURLSession(iOS7以后)来替代。目前使用最广泛的第三方网络框架AFNetworking最新版本已弃用了NSURLConnection,那我们学习它还有什么用呢?

  • 首先,苹果弃用它还是需要时间的,最起码到iOS10之后;
  • 现在还有一些老项目会使用NSURLConnection,特别是2013年之前的项目,用户量基础还是很大的;
  • 另外,不得不承认,有些公司还在用类似ASI这些经典的网络框架,所以还是很有必要学习NSURLConnection的。

二、使用的一般步骤

1 NSURL:请求地址,定义一个网络资源路径:

NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?参数&参数"];

解释如下:

  • 协议:不同的协议,代表着不同的资源查找方式、资源传输方式,比如常用的http,ftp等
  • 主机地址:存放资源的主机的IP地址(域名)
  • 路径:资源在主机中的具体位置
  • 参数:参数可有可无,也可以多个。如果带参数的话,用“?”号后面接参数,多个参数的话之间用&隔开

2 NSURLRequest:请求,根据前面的NSURL建立一个请求:

NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];

参数解释如下:

  • url:资源路径
  • cachePolicy:缓存策略(无论使用哪种缓存策略,都会在本地缓存数据),类型为美剧类型,取值如下:
    • NSURLRequestUseProtocolCachePolicy = 0 //默认的缓存策略,使用协议的缓存策略
    • NSURLRequestReloadIgnoringLocalCacheData = 1 //每次都从网络加载
    • NSURLRequestReturnCacheDataElseLoad = 2 //返回缓存否则加载,很少使用
    • NSURLRequestReturnCacheDataDontLoad = 3 //只返回缓存,没有也不加载,很少使用
  • timeoutInterval:超时时长,默认60s

另外,还可以设置其它一些信息,比如请求头,请求体等等,如下:

注意,下面的request应为NSMutableURLRequest,即可变类型

// 告诉服务器数据为json类型
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
// 设置请求体(json类型)
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} options:NSJSONWritingPrettyPrinted error:nil];
request.HTTPBody = jsonData; 

3 发送请求:

NSURLConnection默认的请求类型为GET,下面分异步和同步两种介绍

异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    // 有的时候,服务器访问正常,但是会没有数据!
    // 以下的 if 是比较标准的错误 处理代码!
    if (connectionError != nil || data == nil) {
        //给用户的提示信息
        NSLog(@"网络不给力");
        return;
    }
}];

参数说明如下:

  • completionHandler:请求响应后(或者请求超时)执行的代码,queue为代码添加到的队列,即block执行的线程
    • NSURLResponse 为服务器的响应,真实类型为NSHTTPURLResponse,通常只在“下载”功能时,才会使用;下面是协议头的参数:
      • URL:响应的URL,有的时候,访问一个URL地址,服务器可能会出现重定向,会定位到新的地址!
      • MIMEType(Content-Type):服务器告诉客户端,可以用什么软件打开二进制数据!网络之所以丰富多采,是因为有丰富的客户端软件!栗子:windows上提示安装 Flash 插件
      • expectedContentLength:预期的内容长度,要下载的文件长度,下载文件时非常有用
      • suggestedFilename:"建议"的文件名,方便用户直接保存,很多时候,用户并不关心要保存成什么名字!
      • textEncodingName:文本的编码名称 @"UTF8",大多数都是 UTF8
      • statusCode:状态码,在做下载操作的时候,需要判断一下
      • allHeaderFields:所有的响应头字典时候,用户并不关心要保存成什么名字!
    • NSData 服务器返回的数据,例如json、xml(现在用的少)
    • NSError 网络访问错误码

注意,block的执行线程为queue,如果block涉及到UI操作,则必须回到主线程:[NSOperationQueue mainQueue]

发送同步请求
// 同步请求,代码会阻塞在这里一直等待服务器返回,如果data为nil则请求失败,当获取少量数据时可以使用此方法。
// request参数同上。
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; 

三、举例说明

分三个部分:

  1. 网络请求(json、xml数据)
  2. 文件下载
  3. 文件上传,这里例子不再给出,请参考这里:文件上传

1 一般的网络请求

/// 两种请求方式
typedef enum {
    MethodGET,
    MethodPOST
}Method;

/// 请求成功后,直接将jsonData转为jsonDict
+ (void)connectionRequestWithMethod:(Method)method   URLString:(NSString *)URLString parameters:(NSDictionary *)dict success:(void (^)(id JSON))success fail:(void (^)(NSError *error))fail {
    URLString = [URLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // 1.创建请求
    NSURL *url = [NSURL URLWithString:URLString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 请求方式,默认为GET
    if (method == MethodPOST) {
        request.HTTPMethod = @"POST";
    }
    // 根据需要设置
    [request setValue:@"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" forHTTPHeaderField:@"Accept"];
    
    // 2.设置请求头 Content-Type  返回格式
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
    // 3.设置请求体 NSDictionary --> NSData
    if (dict != nil) {
        NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
        request.HTTPBody = data;
    }
    // 4.发送请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        if ((data != nil) && (connectionError == nil)) {
            NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            if (success) {
                success(jsonDict);
            }
        } else {
            if (fail) {
                fail(connectionError);
            }
        }
        
    }];
}

2 文件下载

下面举一个实现断点续传的网络下载,要实现断点续传,还需要用到NSURLConnectionDataDelegate代理,NSFileHandle文件操作或者数据流的形式(NSOutputStream),这里以NSFileHandle为例,访问的服务器采用Apache搭建的本地服务器,具体见代码:

@interface ViewController () <NSURLConnectionDataDelegate>

/// 文件下载完毕之后,在本地保存的路径
@property (nonatomic, copy) NSString *filePath;
/// 需要下载的文件的总大小!
@property (nonatomic, assign) long long serverFileLength;
/// 当前已经下载长度
@property (nonatomic, assign) long long localFileLength;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];    
}
/// 点击屏幕事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self downloadFile];
}
/// 下载文件
- (void)downloadFile {
    // 文件存储路径
    self.filePath = @"/Users/username/Desktop//陶喆 - 爱很简单.mp3";
    // 要下载的网络文件,采用Apache搭建的本地服务器
    NSString *urlStr = @"http://localhost/陶喆 - 爱很简单.mp3";
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 设置请求方法为 HEAD 方法,这里只是头,数据量少,用同步请求也可以,不过最好还是用异步请求
    request.HTTPMethod = @"HEAD";
    // 无论是否会引起循环引用,block里面都使用弱引用
    __weak typeof(self) weakSelf = self;
    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        // 出错则返回
        if (connectionError != nil) {
            NSLog(@"connectionError = %@",connectionError);
            return ;
        }
        // 记录需要下载的文件总长度
        long long serverFileLength = response.expectedContentLength;
        weakSelf.serverFileLength = serverFileLength;
        // 初始化已下载文件大小
        weakSelf.localFileLength = 0;
        
        NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:weakSelf.filePath error:NULL];
        
        long long  localFileLength = [dict[NSFileSize] longLongValue];
        
        // 如果没有本地文件,直接下载!
        if (!localFileLength) {
            // 下载新文件
            [weakSelf getFileWithUrlString:urlStr];
        }
        // 如果已下载的大小,大于服务器文件大小,肯定出错了,删除文件并从新下载
        if (localFileLength > serverFileLength) {
            // 删除文件 remove
            [[NSFileManager defaultManager]  removeItemAtPath:self.filePath error:NULL];
            
            // 下载新文件
            [self getFileWithUrlString:urlStr];
            
        } else if (localFileLength == serverFileLength) {
            NSLog(@"文件已经下载完毕");
        } else if (localFileLength && localFileLength < serverFileLength) {
            // 文件下载了一半,则使用断点续传
            self.localFileLength = localFileLength;
            
            [self getFileWithUrlString:urlStr WithStartSize:localFileLength endSize:serverFileLength-1];
        }
    }];
    
}
/// 重新开始下载文件(代理方法监听下载过程)
-(void)getFileWithUrlString:(NSString *)urlString
{
    NSURL *url = [NSURL URLWithString:urlString];
    // 默认就是 GET 请求
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    // 开始请求,并设置代理为self
    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
    // 设置代理的执行线程,一般不在主线程
    [conn setDelegateQueue:[[NSOperationQueue alloc] init]];
    
    // NSUrlConnection 的代理方法是一个特殊的事件源!
    // 开启运行循环!
    CFRunLoopRun();
}

/* 断点续传(代理方法监听下载过程)
 * startSize:本次断点续传开始的位置
 * endSize:本地断点续传结束的位置
 */
-(void)getFileWithUrlString:(NSString *)urlString WithStartSize:(long long)startSize endSize:(long long)endSize
{
    // 1. 创建请求!
    NSURL *url = [NSURL URLWithString:urlString];
    // 默认就是 GET 请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    // 设置断点续传信息
    NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld",startSize,endSize];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    // NSUrlConnection 下载过程!
    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
    // 设置代理的执行线程// 传一个非主队列!
    [conn setDelegateQueue:[[NSOperationQueue alloc] init]];
    
    // NSUrlConnection 的代理方法是一个特殊的事件源!
    // 开启运行循环!
    CFRunLoopRun();
}

#pragma mark - NSURLConnectionDataDelegate
/// 1. 接收到服务器响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 服务器响应
    // response.expectedContentLength的大小是要下载文件的大小,而不是文件的总大小。比如请求头设置了Range[requestM setValue:rangeStr forHTTPHeaderField:@"Range"];则返回的大小为range范围内的大小
}
/// 2. 接收到数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    //data当前接收到的网络数据;
    // 如果这个文件不存在,响应的文件句柄就不会创建!
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
    // 在这个路径下已经有文件了!
    if (fileHandle) {
        // 将文件句柄移动到文件的末尾
        [fileHandle seekToEndOfFile];
        // 写入文件的意思(会将data写入到文件句柄所操纵的文件!)
        [fileHandle writeData:data];
        
        [fileHandle closeFile];
        
    } else {
        // 第一次调用这个方法的时候,在本地还没有文件路径(没有这个文件)!
        [data writeToFile:self.filePath atomically:YES];
    }
}
/// 3. 接收完成
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"下载完成");
}
/// 4. 网络错误
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"下载错误 %@", error);
}
@end

四、NSData 数据的反序列化

服务器返回的NSData数据类型,事先已经知道,因此可以直接返序列化为实际的类型,例如:json(字典或数组).plist(字典或数组)textxml等:

  • 加载数据
// 加载本地
NSString *path = [[NSBundle mainBundle] pathForResource:@"文件名 " ofType:nil];
NSData *jsonData = [NSData dataWithContentsOfFile:path];

// 从网络直接加载
NSURL *url = [[NSBundle mainBundle] URLForResource:@"topic_news.json" withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];

  • .plist反序列化

这种很少使用,只是苹果自己的格式

// 关于选型参数
// NSPropertyListImmutable = 0,不可变  
// NSPropertyListMutableContainers = 1 << 0,容器可变
// NSPropertyListMutableContainersAndLeaves = 1 << 1,容器和叶子可变
// 通常后续直接做字典转模型,不需要关心是否可变,所以一般直接赋值为0
id result = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
  • 关于json数据

    • json数据的本质是字符串,根格式只有两种可能:数组或字典。

      • 反序列化:从服务器接收到的二进制数据 转换成 字典或者数组
      // 假设json数据位:[{键值对},{键值对}],则可以将数据转化为字典或数组
      

    NSArray *dictArray = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
    ```

      - 序列化:将字典或者数组 转换成 二进制数据,准备发送给服务器  
    
      ```objc
    

// obj为字典或数组
NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:0 error:NULL];
// 转化前可以判断是否可以序列化:
(BOOL)isValidJSONObject:(id)obj;
```
* 一个对象能够被转换成 JSON 必须符合以下条件:
- 顶级节点,必须是一个 NSArray or NSDictionary
- 所有的对象必须是 NSString, NSNumber, NSArray, NSDictionary, or NSNull
- 所有字典的 key 都必须是 NSString
- NSNumber 不能为空或者无穷大

注意事项:

  • 请求的缓存策略使用 NSURLRequestReloadIgnoringCacheData,忽略本地缓存
  • 服务器响应结束后,要记录 Etag,服务器内容和本地缓存对比是否变化的重要依据!
  • 在发送请求时,设置 If-None-Match,并且传入 etag
  • 连接结束后,要判断响应头的状态码,如果是 304,说明本地缓存内容没有发生变化,此时可以使用本地缓存来加载数据,如果缓存文件被意外删除,程序依然运行但会报错,加载数据也失败:
NSCachedURLResponse *cachedURLRes = [[NSURLCache sharedURLCache] cachedResponseForRequest:requestM];//cachedURLRes为nil
  • GET 缓存的数据会保存在 Cache 目录中 /bundleId 下,Cache.db 中

iOS9访问http网页

  1. 在Info.plist中添加NSAppTransportSecurity类型Dictionary;
  2. 在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES

参考:http://www.cnblogs.com/mddblog/p/5134783.html

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 阅读目录 一、整体介绍 二、使用的一般步骤 三、举例说明 四、NSData 数据的反序列化 注意事项: iOS9访...
    九洲仙人阅读 791评论 0 0
  • iOS开发系列--网络开发 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可...
    lichengjin阅读 3,650评论 2 7
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,333评论 0 12
  • 最近又开始上演小鲜肉仙侠剧了,追剧的时候怎能没有啤酒和鸡脚呀! 外面卖的那又白又胖的泡鸡脚,你要是想好好的多活几年...
    pauline布丁妈阅读 649评论 4 7