NSRULConnection网络应用原理

获取网络数据原理.

1. 请求两种方式对比

  1. NSData方式: 可以根据URL直接获取JSON数据, 但是无法设置缓存策略,超时时长等求情头属性;
  2. NSURLConnection: 可以异步的发送请求, 通过NSMutableURLRequest 自定义请求头 来设置缓存策略和等待时长,请求行等;

自定义请求头可以实现很多功能,我们已经学过两个请求头ConnectionUser-Agent分别可以用来设置长短连接设备信息。一般使用kvc方式设置setValue: forHTTPHeaderField:

几种缓存策略:

  1. 0 NSURLRequestUseProtocolCachePolicy 协议缓存,根据response中的Cache-Control字段判断缓存是否有效,如果缓存有效则使用缓存数据否则重新从服务器请求
  2. 1-忽略缓存,重新下载 NSURLRequestReloadIgnoringLocalCacheData
  3. 2-NSURLRequestReturnCacheDataElseLoad 直接使用缓存数据,如果没有缓存则重新请求下载
  4. 3- eNSURLRequestReturnCacheDataDontLoad 直接使用缓存数据,如果没有就没有;

超时时长:

  • 默认超时时长:60s
  • 一般设置成15~45之间

Connection常见状态码stautcode

  • 2xx - 成功: 表明服务器成功接收客户端请求
  • 3xx - 重定向: 客户端浏览器必须采取更多操作来实现请求
  • 4xx - 客户端错误;
  • 5xx - 服务器错误;
  • 具体参考http://jasonxiawanjian.iteve.com/blog/1884562;

XCode7默认无法发送HTTP请求
xcode7 提供的ATS(App Transport Security)功能,默认不允许发送HTTP请求
可以在PList中添加

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>

2. 获取网络数据 - JSON/XML格式

2.1 JOSN–常用

JavaScript Object Notation(JavaScript中对象);
通常在网络传输数据的时候会使用JSON或XML,JSON比XML更轻量级使用更加广泛;
JSON具有自我描述性,更易理解;独立于语言,其他语言一般都有解析JSON的库;

语法规则:

{“className”:”一班” , “students”:[{“name”:”zs”,”age”:”18”}, {“name”:”zs”, “age”:”18”}]}

2.2 解析: NSJSONSerialization.

JSON的序列化就是将数据转换成互联网传输的二进制数据的过程,
反序列化就是将互联网传输的二进制数据转换成JSON根节点所代表的OC类型数据的过程。

id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];//id  根据JSON根节点类型是数组还是字典设置;  之后直接转模型即可;

关于参数

options:序列化选项,枚举类型,但是可以指定为枚举以外的类型,例如指定为0则可以返回NSDictionary或者NSArray
     1.NSJSONReadingMutableContainers:返回NSMutableDictionary或NSMutableArray
     2.NSJSONReadingMutableLeaves:返回NSMutableString字符串 iOS7后不起作用
     3.NSJSONReadingAllowFragments:可以解析JSON字符串的外层既不是字典类型(NSMutableDictionary、NSDictionary)又不是数组类型(NSMutableArray、NSArray)的数据,但是必须是有效的JSON字符串
     一般默认选择0, 直接反序列化不可变对象以 提高性能;

注意:如果JSON中有中文(基本都有),打印出来的是经过Unicode编码;我们可以扩展NSArray和字典的非正式协议.重写descriptionWtihLocale方法; 来测试打印结果是否正确获取了数据;

类似的,可重写自定义类的description方法来打印需要的数据格式;

- ( NSString *)description {
NSString *str = [NSString stringWithFormat:@"%@{name:%@,age:%d}",[super description],self.name,self.age.intvalue];
return str;
}   

引申的:plist也有序列化和反序列化,不过系统自动封装了方法.例:dataWithPropertyList

第三方框架解析 JSON

由于iOS5之前,系统自带解析框架效率不高,使用第三方JSONKit框架;iOS之后可以使用系统的;所以了解下即可;NSDictionary *dic = [[JSONDecoder decoder] objectWithData:data]; 使用前导入框架,设置编译方式MRC;-fno-obj-arc

字典转模型:

JSON解析后的集合存储的都是对象,所以网络数据有数字的话,模型属性要用NSNumber;
对应情况下:

  1. 如果模型属性多于 JOSN数据; KVC可以使用; 剩下的自己赋值 ;
  2. 如果模型属性少于网络数据属性, KVC使用会报错; 此时 需要重写setValue forUndefinedKey 空;

2.2 XML和HTML区别

Extentsible Markup Language(可扩展标记语言); 
非常类似于HTML,不过HTML的作用是显示数据,XML的目的是存储和传输数据;  
html的标签是预定义好的
xml的标签是自己定义的
XML是各种应用之间传输数据最常用的格式(**以前**

XML格式:

<Person color="yellow" weight="130" height="150">
                <name>wg</name>
                <age>108</age>
            </Person>
            <Student  name="y" />

解析方式:

  1. SAX:iOS上解析,速度快内存占用小,只读
  2. DOM:mac上解析方式,iOS无法直接使用,解析内存占用大,读写;

SAX解析:

解析是在代理方法中进行的;
设置代理 : parer.delegate = self;
执行代理方法: [parer parser];

代理调用顺序方法如下:
//具体代码看文件夹
//1.开始解析文档
//2.找开始节点 根节点开始
2.1 找开始节点 -下一级
//3.找节点之间的内容
4.找结束节点-下一级
循环
//4.找结束节点 根节点结束
//5.结束解析;

注意:*KVC赋值的过程就是地址指向的过程,不会做类型转换; 所以把所有属性都使用 copy*;否则的话KVC赋值的value是NSMutableString的时候会有问题;

DOM解析:
在iOS解析DOM的话需第三方框架: GData/KissXML 适合读写比较小得XML文件;详见文档.

文件上传

使用POST请求; 默认不能上传大文件(2M以上)—协议 ; 因为没有断点;
请求头的Content-Type告诉服务器,客户端post过去的数据的样子:

  • application/x-www-form-urlencoded:告诉服务器post过去的数据的样式是 键=值&键=值 的形式(和URL传参的样式一致) 默认使用此样式.
  • multipart/form-data; boundary=—-xxx: 键值对无法上传文件,所以使用form-data;xxxx是一个随机内容,不同的浏览器生成的是不一样的,我们也可以自己指定
  • text/plain:普通文本数据类型,支持浏览器访问,发送前其中的空格替换为“+”,但是不对特殊字符编码。
  • application/json:json数据类型,浏览器访问不支持 。
  • text/xml:xml数据类型,浏览器访问不支持。

其他上传文件方式: 第三方框架(AFN,ASI)/ 自己上传文件.

请求体中各项含义

------WebKitFormBoundaryuWw18YzUxr2ygEJi  //分隔符 
Content-Disposition: form-data; name="userfile"; filename="03.JPG" 
Content-Type: image/jpeg  //文件类型

二进制数据
------WebKitFormBoundaryuWw18YzUxr2ygEJi--

name–表单的那么属性值;
filename – 传递给服务器的文件名;
Content-Type – 告诉服务器传递的文件类型: (text/plain , image/jpeg , image/jpg , image/png , application/octet-stream 等)

RESTful - 面试

使用不同的HTTP访问方法,请求一个URL,表达不同语义;
主要服务器端开发,我们使用;
RESTful风格让url的可读性更好

示例:http://www.ooxx.com/video/cls

  1. GET:–获取服务器上cls的所有视频
  2. POST:新增服务器上cls的视频
  3. **PUT:修改**xxxx
  4. **DELETE:删除**xxx
上传额外信息

上传文件时,有时希望传递一些附加信息给服务器,这个时候可以在上传文件的同时再post过去这些附加信息;
例如:上传图片时给照片添加图片的作者,名称;发微博的时候上传图片,还要附带 微博内容、作者、地理位置等信息;

这里,就要用到了JSON序列化
把要上传的信息转换JSON形式字符串二进制数据;
JSON序列化只能用于OC字符串,以及字符串元素的数组和字典:(+(BOOL)isValidJSONObject:(id)obj; 判断对象是否可以被序列化.)而通常我们需要传递的信息都是模型数据:

即自定义对象转JSON:

//1.模型 转 字典
NSDictionary *dic = [movie dictionaryWithValuesForKeys:@[@"name",@"movieName",@"size",@"_isYellow"]] //属性列表.
//2. 字典 JSON序列化 (可以用方法判断下能不能序列化)
[NSJSONSerialization dataWithJSONObject:dic options:0 error:NULL]

**注意:**KVC不仅可以给属性赋值,也可以给成员变量赋值;属性有setget方法,

JSON序列化也可以用来保存JSON文件;

JSON保存到文件
NSDictionary *dic = @{@"name":@"zs",@"age":@(18)};

    NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:0 error:NULL];
    [data writeToFile:@"/Users/xxxxx/Desktop/2222222.json" atomically:YES];

从加载JSON文件转换成对象
NSData *data = [NSData dataWithContentsOfFile:@"/Users/xxxxx/Desktop/2222222.json"];
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    NSLog(@"%@",dic);

文件断点下载

实际开发文件下载的时候不管是通过代理方法还是静态方法执行请求和响应,我们都会分批请求数据,而不是一次性请求数据。
原因

  1. 意外断开又要从0下载;
  2. 内存暴涨,造成程序闪退
  3. 没有下载进度提示.

在网络开发中可以在请求的头文件中设置一个range信息,它代表请求数据的大小,通过字段配合服务器端可以精确的控制每次服务器响应的数据范围;这样,只要每次发送请求控制这个头文件信息就可以做到分批请求;
通过HEAD请求获取响应头信息(); 针对不同的开发技术,服务器端处理方式稍有差别,但是基本原理是一样的,那就是读取Range信息,按需提供相应数据。

NSURLResponse 属性

expectedContentLength : 文件总大小;
suggestedFilename : 建议保存的文件名字;

1. 使用NSURLConnection代理方法 下载.

NSURLConnectionDownloadDelegate 方法.

此代理专门用来处理报刊杂志的下载,但是下载完成看不到文件

//设置connection的代理
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //开始
    [conn start];

代理方法
//下载进度
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes{
    NSLog(@"%f", totalBytesWritten*1.0f / expectedTotalBytes);
}
//下载完成
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL{
    NSLog(@"下载完成--%@",destinationURL);
}
//下载出错
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"错误");
}
所以我们要使用另一个代理协议:NSURLConnectionDataDelegate

代理方法如下: 具体操作写在对应方法里.断点下载, 方法2会在调用多次

//接收到响应头
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    //通过响应头获取文件的大小
    NSLog(@"%lld",response.expectedContentLength);
    self.expectedContentLength = response.expectedContentLength;
}
//持续接收数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    self.currentFileSize += data.length;
    NSLog(@"%f",self.currentFileSize*1.0f/self.expectedContentLength);
}
//下载完成
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    NSLog(@"over");
}
//下载出错
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
    NSLog(@"错误");
}

为了解决内存暴涨问题,我们每次接收到部分二进制文件数据后,就直接保存到文件; 所以需要引入保存文件的类

NSFileHandle 和 NSOutputStream 解决断点存储

1. NSFileHandle

NSFileHandle是一个专门用于读写文件的类,有一个offset属性,默认指向开头.可以设这offset来断点存储数据; 称为文件指针
//NSFileManager 负责文件的 删除、复制、是否存在等操作

//NSFileHandle需要有文件才能创建,如果文件不存在,那么file为nil.
NSFileHandle *file = [NSFileHandle fileHandleForWritingAtPath:filePath]; 
if(file == nil) {//说明文件还没下载,直接写入data来创建文件
      [data writeToFile:filePath atomically:YES];
} else { //接着下载,那么 把文件指针移到末位,存入新数据
      [file seekToEndOfFile]; //可以移到指定位置 seekToFileOffset:(all)length
      //写入文件
      [file writeData:data]

   //最后 注意 存储完 需要关闭句柄
   [file closeFile]
}

2. NSOutputStream 输出流

NSOutputStream继承NSStream.(使用open/close方法);
相当于在内存和硬盘之间创建一个管道,用来运送一些字节. 用法较上一个简单点,但是只能连续顺序存储;

下载完响应头之后  //初始化流
self.stream = [NSOutputStream outputStreamToAtPath:path append:YES]; [self.stream open];//打开流 下载完成或是出错代理方法中, close流 下载过程代理方法中.写入文件 [self.stream write:data.bytes maxLength:data.length]; 

文件下载基本逻辑 -为了解决问题1.

  1. 如果没有本地文件–>下载
  2. 如果本地文件存在–> 跟服务器文件进行大小比较.
    1. 本地大小 == 服务器大小 –> 不下载 或是 重新下载新文件
    2. 本地大小 < 服务器大小 –> 断点继续下载;
    3. 本地大小 > 服务器大小 –> 错误, 重新下载,看情况删除本地文件;
//获取服务器上文件的信息(文件名和文件大小)
- (void)checkServerInfo:(NSURL *)url{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"head";
    NSURLResponse *response = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    //通过属性记录文件的大小,拼接要保持的文件的路径
    self.expectedContentLength = response.expectedContentLength;
    self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
//获取本地文件大小
- (long long)checkLocalInfo{
    NSFileManager *manager = [NSFileManager defaultManager];
    long long fileSize = 0;
    //检查是否有文件
    if ([manager fileExistsAtPath:self.targetPath]) {
        //获取文件大小
        NSDictionary *attrsDic = [manager attributesOfItemAtPath:self.targetPath error:NULL];
        fileSize = attrsDic.fileSize;
        //获取文件大小
// NSLog(@"%lld",[attrsDic[NSFileSize] longLongValue]);
// NSLog(@"%lld",attrsDic.fileSize);
    }
    if (fileSize > self.expectedContentLength) {
        //如果本地的文件大小。比服务器的大。删除
        [manager removeItemAtPath:self.targetPath error:NULL];
        fileSize = 0;
    }
    return fileSize;
}

注意:
1. 这里字典能点出fileSize是因为NSURLConnectionDelegate里面对字典做了扩展,方便我们使用.
2. 对比判断后,选择断点继续下载, 从指定的偏移处开始下载. 断点下载的核心就是设置range头,

     //bytes=x-y 从x字节开始下载,下载到y字节
    //bytes=x- 从x字节开始下载,直到最后
    //bytes=-x 从0字节开始下载,下载到x字节
    NSString *range = [NSString StringWithFormat:@"bytes=%lld-",offset];
    [request setValue:range forHTTPHeaderField:@"range"];
    //设置connect代理
    NSURLConnection *conn = [NSURLCOnnection connectionWithRequest:request delegate:self];
    //开始
    [conn start]

问题三,进度条设置.

创建个自定义的button,使用drawRect重绘来做进度显示;给按钮title赋值时, 系统默认的button会有点击效果,所以尽量用自定义;

setProcess:  中调用重绘
- (void)drawRect : {
开始弧度是   -M_PI_2;
endAngle = 2*M_PI*self.process + startAngle ;
[path addArcWithCenterxxx]方法画出进弧形度条;

扩展:1.可以在sb里面设置初始值,KeyPath
2. IB_DESIGNABLE 可以设计
属性设置时加上,可以在左边属性栏显示出此属性;
3.暂停下载,调用[self.conn cancel]即可,因为我们已经做到了断点下载.直接取消;

代码重构

Downloader只能下载一个文件, 下载多个任务,时,我们需要一个新的Downloader; 为了方便管理,引入单例管理类,类中用字典集合来存储downloader;
因为, 下载要按顺序 先发送请求获取服务器文件大小,才能下载,而这是耗时的操作, 需要在子线程中执行 , 但是这样又难以线程同步,所以把Downloader改成NSOperation类,这样,让其成为单纯的操作,易于设置最大并发数等.
所以把所有下载操作放到一个操作线程里面;这样也能防止歧义,不必要别人用我们的方法还要注意调用顺序; 且NSOperation 很多功能,最大并发数, 重写main;
解决点击多次重复下载问题:加入操作缓存池, 下载文件是判断下 取出之前的操作即可

你可能感兴趣的:(NSRULConnection网络应用原理)