iOS开发经验(13)-网络

2017-02-14  本文已影响190人  Ryan___

目录

  1. 网络基本概念
  2. TCP/IP协议簇基本概念
  3. HTTP
  4. 网络开发技术解决方案
  5. 数据解析
  6. 网络优化
1. 网络基本概念

为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了“开放系统互联参考模型”,即著名的OSI/RM模型。它将计算机网络体系结构的通信协议划分为七层:

| 协议 | 具体
| -------------
| 应用层 | OSI 的应用层协议包括文件的传输、访问及管理协议(FTAM) ,以及文件虚拟终端协议(VIP)和公用管理系统信息(CMIP)等;规定数据的传输协议;eg:RJ45等将数据转化成0和1
| 表示层 | 表示层供多种功能用于应用层数据编码和转化,以确保以一个系统应用层发送的信息 可以被另一个系统应用层识别,可以理解为:解决不同系统之间的通信,eg:Linux下的QQ和Windows下的QQ可以通信􏰁
| 会话层 | 会话层建立、管理和终止表示层与实体之间的通信会话;建立一个连接(自动的手机信息、自动的网络寻址);
| 传输层 | 传输层向高层􏰁提供可靠的端到端的网络数据流服务。可以理解为:每一个应用程序都会在网卡注册一个端口号,该层就是端口与端口的通信!常用的(TCP/IP)协议;
| 网络层 | 网络层负责在源和终点之间建立连接;可以理解为,此处需要确定计算机的位置,怎么确定?IPv4,IPv6!
| 数据链路层 | 数据链路层通过物理网络链路􏰁供数据传输。不同数据链路层定义了不同的网络和协 议特征,其中包括物理编址、网络拓扑结构、错误校验、数据帧序列以及流控;可以简单的理解为:规定了0和1的分包形式,确定了网络数据包的形式;
| 物理层 | 物理层负责最后将信息编码成电流脉冲或其它信号用于网上传输;eg:RJ45等将数据转化成0和1;

其中第四层完成数据传送服务,上面三层面向用户。对于每一层,至少制定两项标准:服务定义和协议规范。前者给出了该层所提供的服务的准确定义,后者详细描述了该协议的动作和各种有关规程,以保证服务的提供。

网络层次分层.png
2. TCP/IP协议簇基本概念

基本概念
TCP/IP协议簇(协议簇通常指彼此相关联的一系列协议的总称),译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
不同于OSI/RM的七层结构,TCP/IP模型是一个四层模型,从上到下依次是:

应用层
传输层
网络层
物理链路层

值得一提的是,传输层可以使用两种不同的协议,一个是面向连接的传输控制协议(TCP),另一个是无连接的用户数据报协议(UDP),我们耳熟能详的的协议如HTTP、FTP、POP3、DNS等都属于应用层的协议,它们要么构建在TCP之上,要么构建在UDP之上。

| 协议 | 具体
| -------------
| 应用层协议 | FTP · HTTP· XMPP ·POP3 · SMTP
| 传输层协议 | TCP · UDP · TLS
| 网络层协议 | IP (IPv4 · IPv6)
| 物理链路层 | Wi-Fi· GPRS·以太网 · 调制解调器

常见的应用层协议:

| 协议 | 端口 | 说明
| -------------
| HTTP | 80 | 超文本传输协议
| HTTPS | 443 | HTTP+SSL
| POP3 | 110 | 邮件协议

传输层
传输层(Transport Layer)是TCP/IP协议簇(四层模型)中最重要、最关键的一层,它负责总体的数据传输和数据控制的一层,传输层提供端到端(应用会在网卡注册一个端口号)的交换数据的机制,检查分组编号与次序。传输层对其上三层如会话层等,提供可靠的传输服务,对网络层提供可靠的目的地站点信息。

TCP

UDP

IP地址
IP地址[主机名],英文全称:Internet Protocol Address,又译为网际协议地址。IP协议的作用就是把各种数据包传给对方。为了能确保准确送达,所以需要IP地址和MAC地址,IP地址指明了节点被分配的地址,MAC地址指的是网卡所属的固定地址,IP地址可变换,但是MAC地址是不会改变的。所以IP间的通信就需要MAC地址,这里就需要知道IP地址和MAC地址的对应关系,所以又出现了另一种协议ARP(Address Resolution Protocol),能通过IP地址查询到MAC地址。
1.网络中设备的标识,用来唯一标识每一台计算机。通常现在常用的IP地址是IPV4地址。
2.IPV4就是有4段数字,格式是xxx.xxx.xxx.xxx,每一段数字由8位二进制做成,取值范围是0~255。
3.IPV4采用32位地址长度,只有大约43亿个地址,IPv4定义的有限地址空间将被耗尽。
4.为了扩大地址空间,拟通过IPV6重新定义地址空间。IPv6采用128位地址长度,几乎可以不受限制地提供地址,但IPV6现在还没有正式普及。
为了解决IPV4有限地址空间的问题,IP地址又分内网地址和外网地址。(比如校园网,每一个学生都会有一个内网地址,学校会有一个路由器,路由器会有个外网地址,学生想要上外网都必须通过路由器出去,只要通过同一个路由器出去的,他们对应的外网地址都是一样的)
本质上所有的网络访问是通过ip地址访问的,域名是一个速记符号,不用记住IP地址复杂的数字。
本地回环地址:127.0.0.1 主机名:localhost
5.每台计算机都有一个127.0.0.1
如果127.0.0.1 ping不通,说明网卡不工作(比如装黑苹果,检测网卡驱动有没装好,可以 ping下回环地址)
如果本机地址 ping不通,说明网线坏了。

端口号
端口号用来标识进程的逻辑地址,不同进程的标识。
有效的端口:0~65535。
其中0~1024由系统使用或保留端口。
开发中不要使用1024以下的端口。
(端口有什么用呢?我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区 分不同的服务的。)

** DNS 「Domain Name System」域名系统协议**
DNS 就是进行域名解析的服务器
DNS 命名用于 Internet 等 TCP/IP 网络中,通过用户友好的名称查找计算机和服务
用于命名、组织到域层次结构中的计算机和网络服务,可以简单地理解为 将 URL 转换为 IP 地址
域名:是由圆点分开一串单词或缩写组成的,域名与 IP 地址之间是一一对应的

Socket 简介
Socket(套接字)是通信的基石,是 应用层 和 传输层 之间的桥梁,是支持TCP/IP协议的网络通信的基本操作单元,网络通信其实就是 Socket 间的通信「数据在两个 Socket 间通过 IO 传输」。
HTTP 支持短链接 Socket 支持长链接 用于IM。
Socket包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际应用中,客户端到服务器之间的通信防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。

而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

HTTP 与 Socket 的区别

Socket通信.png
Socket 的连接过程
长连接:指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包
短连接:指通讯双方有数据交互时,就建立一个连接,数据发送完成后,则断开此连接,即每次连接只完成一项业务的发送
3. HTTP协议

后来发明了浏览器,浏览器通过超文本传输协议(HTTP)跟服务器交换超文本数据,通过图形用户界面显示从服务器获得的超文本数据,这一切都让使用Internet变得无比简单,于是计算机网络的用户数量开始爆炸式的增长。
我们先来解释一下什么是协议以及HTTP到底是一个怎样的协议。我们将任何可发送或接收信息的硬件或程序称之为实体,而协议则是控制两个对等实体进行通信的规则的集合。简单的说,协议就是通信双方必须遵循的对话的标准和规范。HTTP是构建在TCP之上的协议,之所以选择TCP作为底层传输协议是因为TCP除了可以保证可靠通信之外,还具备流量控制和拥塞控制的能力,如果这一点不能理解也不要紧,我么只需要知道HTTP需要可靠的传输层协议的支持就够了。

再次重复一遍:
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。
HTTP协议规定了客户端和服务器之间的数据传输格式.

HTTP优点:

HTTP请求/响应:
HTTP有两种类型的报文:请求报文和响应报文。请求报文和响应报文都是由三个部分组成的。

请求报文是由请求行、请求头和消息体构成的。

响应报文是由响应行、响应头和消息体构成的。

HTTP基本通信过程

请求:客户端向服务器索要数据
响应:服务器返回客户端相应的数据
1.确定请求路径url
http://www.baidu.com:80/tools.html
2.获取主机名
www.baidu.com
3.DNS域名解析
192.168.31.1
4.获得端口号80
5.连接到192.168.31.1的端口80
6.发送HTTP GET请求 
7.接收到服务器的响应
8.关闭连接

HTTP请求方法
HTTP:是应用层的网络传输协议

eg:GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH

其中最常用的是GET和POST方法。
因为GET和POST可以实现上述所有操作,所以,在现实开发中,GET和POST方法使用的最为广泛,除此以外HEAD请求使用频率也比较高。

GET和POST的区别:
1.GET请求:服务器的地址和请求参数 都会出现在请求接口中,也就是说 服务器地址和请求参数共同组成了请求接口,而POST请求,请求参数不会出现在请求接口中,作为请求体,提交给路由器。
2.因为GET请求的请求参数会出现请求接口中所以信息容易被捕获,安全性低,而POST请求的请求参数封装在请求体中,作为二进制流进行传输,安全性高。
3.GET请求的请求接口中,有请求参数,而对于请求接口我们有字节限制,这样我们导致GET请求有一定局限性。所以,对于GET请求只能上传小型数据,而对于POST请求,请求体 理论上可以无限大,所以一般来说,从服务器请求数据用GET,上传数据用POST。

相关API
NSURL
URL的格式如下所示:

协议://域名或IP地址:端口号/路径/资源

下面是百度logo的URL:

http://www.baidu.com:80/img/bd_logo1.png

URL的基本格式 = 协议://主机地址/资源路径

http://www.example.com/img/logo.png
----- ================ ------------
协议名        主机地址      资源路径

http://8.8.8.8:8080/img/bdlogo.gif
----- ============= ---------------
协议名  主机地址+端口    资源路径

说明:端口号是对IP地址的扩展。例如我们的服务器只有一个IP地址,但是我们可以在这台服务器上开设多个服务,如Web服务、邮件服务和数据库服务,当服务器收到一个请求时会根据端口号来区分到底请求的是Web服务还是邮件服务,或者是数据库服务。我们在浏览器中输入URL的时候通常都会省略端口号,因为HTTP协议默认使用80端口,也就是说除非你访问的Web服务器没有使用80端口,你才需要输入相应的端口号。

状态码

| 状态码 | 英文名称 | 描述
| -------------
| 200 | OK | 请求成功
| 400 | Bad Request | 客户端请求的语法错误,服务器无法解析
| 404 | Not Found | 服务器无法根据客户端的请求找到资源
| 500 | Internal Server Error | 服务器内部错误,无法完成请求

HTTPS简介
HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

4. 网络开发技术解决方案

在iOS中,常见的发送HTTP请求的方案包括:

| 名称 | 说明
| -------------
| NSURLConnection | iOS 2.0 推出,用法简单,最古老最经典最直接的一种方案,在 iOS 9.0 被废弃
| NSURLSession | iOS 7 推出,功能比 NSURLConnection 更加强大
| CFNetwork |NSURL 的底层,纯C语言,几乎不用

| 名称 | 说明
| -------------
| AFNetworking |NSURLConnection & NSURLSession 简单易用,提供了基本够用的常用功能,维护和使用者多

1.NSURLConnection
在iOS 7以前,基于HTTP协议联网的操作最终都要由NSURLConnection类来完成。
利用NSURLConnection进行网络请求分为3步:

发送请求主要有两个方法,一个用于发送同步请求,一个用于发送异步请求。

// 发送同步请求的方法:该方法是阻塞式的会卡住线程。
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request 
      returningResponse:(NSURLResponse **)response error:(NSError **)error;

// 发送异步请求的方法
+ (void)sendAsynchronousRequest:(NSURLRequest *)request 
      queue:(NSOperationQueue *)queue 
      completionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *connectionError))handler

提示:同步请求是阻塞式请求,这就意味着同步请求的方法在返回数据之前会一直阻塞;异步请求是非阻塞式请求,当服务器返回数据时可以回调的方式对数据进行处理。如果明白这一点,就很容易理解为什么上面的同步请求方法会返回NSData指针,而异步请求方法没有返回值但有一个Block类型的参数(Block最适合用来书写回调代码)。

1.get 同步:代码正常写法
get 异步:block写法;get异步:代理写法

2.post同步:代码正常写法
post异步 :代理写法; post异步:block写法

3.同步下,都是代码正常写法,异步下,才会用代理或者block
get,post跟数据安全及大小有关系,跟线程无关系。同步异步只跟线程有关系。
4.代理相较于block优势:下载文件可监听文件下载进度

2.NSURLSession
2013的WWDC,苹果推出了NSURLConnection的继任者NSURLSession。与NSURLConnection相比,NSURLsession最直接的改进就是可以配置每个会话(session)的缓存、协议、cookie以及证书策略(credential policy)等,而且你可以跨程序共享这些信息。每个NSURLSession对象都由一个NSURLSessionConfiguration对象来进行初始化,NSURLSessionConfiguration对象代表了会话的配置以及一些用来增强移动设备上性能的新选项。

URL加载系统的API.png

NSURLSession优势:(相较于NSURLConnection)

NSURLSession主要有三个类:

注:NSURLSession的行为由session的类型(取决于NSURLSessionConfiguration类型决定)、task类型以及创建task时,App是运行在前台还是后台

1. NSURLSession
NSURLSession 本身是不会进行请求的,而是通过创建 task 的形式进行网络请求,同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。那么我们就来看看 NSURLSession 都能创建哪些 task 吧。

2. NSURLSessionConfiguration:会话Session控制
如前面所述,NSURLSessionConfiguration代表了会话的配置,该类的三个创建对象的类方法很好的诠释了NSURLSession类设计时所考虑的不同的使用场景。
一般情况下全局的Session我们够用了,但是如果遇到两个连接使用不同配置的情况,就得重新配置session
NSURLSession支持的3种会话配置:

  1. defaultSessionConfiguration (default)
    进程内会话(默认会话),用硬盘来缓存数据
  2. ephemeralSessionConfiguration (ephemeral)
    临时的进程内会话,不会将cookie,缓存储存到本地,只会放在内存中,当应用程序退出后数据也会消失。(浏览器的无痕浏览模式)
  3. backgroundSessionConfiguration (background)
    后台会话,相比默认会话,该会话会在后台开启一个线程进行网络数据处理。
// 返回一个标准的配置,标准配置会使用默认的缓存策略、超时时间等
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
// 返回一个临时性的配置,这个配置中不会对缓存,Cookie和证书进行持久化存储
// 对于实现无痕浏览这种功能来说这种配置是非常理想的
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
// 返回一个后台配置
// 后台会话不同于普通的会话,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务
// 初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:
      (NSString *)identifier

常规配置属性 :

/* 常规属性 - 常规属性有7个  */ 
/*background 模式下需要标识 */
@property (nullable, readonly, copy) NSString *identifier;

/*超时属性 ,用作设置timeoutIntervalForRequest时间内, 如果没有请求数据发送,则请求超时 */
@property NSTimeInterval timeoutIntervalForRequest;

/* 超时属性, 用作设置timeoutIntervalForResource时间内,如果没有返回响应,则响应超时 */
@property NSTimeInterval timeoutIntervalForResource;

/* 网络服务类型 */
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
    NSURLNetworkServiceTypeDefault = 0, // Standard internet traffic
    NSURLNetworkServiceTypeVoIP = 1, // Voice over IP control traffic  
    NSURLNetworkServiceTypeVideo = 2, // Video traffic
    NSURLNetworkServiceTypeBackground = 3, // Background traffic
    NSURLNetworkServiceTypeVoice = 4    // Voice data
};
// 这个属性一般用于音频处理流
@property NSURLRequestNetworkServiceType networkServiceType;

// 设置这个属性, 会为所有的NSURLSessionTask加上基础request头,可以统一请求头
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;

// 一个布尔值,确定是否通过蜂窝网络连接, 默认为YES , 
@property BOOL allowsCellularAccess;

// iOS8.0后才能用, backgroundSessionConfigurationWithIdentifier中的identifier中对应使用, 被标识的文件,会在后台中下载
@property (nullable, copy) NSString *sharedContainerIdentifier NS_AVAILABLE(10_10, 8_0);

使用NSURLSessionConfiguration配置一个默认session的具体方法

NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.allowsCellularAccess = true;//是否允许蜂窝网络下载(2G/3G/4G)
[sessionConfig setHTTPAdditionalHeaders: @{@"Accept": @"application/json"}];//所有的请求只接收JSON数据
sessionConfig.timeoutIntervalForRequest = 25.0f;//请求超时时间
sessionConfig.timeoutIntervalForResource = 60.0;
sessionConfig.HTTPMaximumConnectionsPerHost = 1;//限制一个主机只有一个网络连接。

//创建会话,指定配置和代理
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig 
                                                  delegate:self 

3. NSURLSessionTask
NSURLSession在使用上需要先根据会话对象(NSURLSession)创建一个请求NSURLSessionTask(会话任务。NSURLSessionTask是抽象类,我们需要使用它的子类,先看看关系:

NSURLSession类继承关系.png

会话任务有三个子类对应不同的场景,分别是:

说明:代理相较于block优势:下载文件可监听文件下载进度!

已经有了NSURLSessionDataTask, 为什么还有继承多一层NSURLSessionUploadTask?

答:简化了操作!从两者提供的对应task 生成的方法能看。比如使用dataTask来进行上传任务的时候,需要指定HTTPMethod为POST或PUT,并且提供的数据(NSData)得赋值给request.HTTPBody。而使用uploadTask来进行上传任务的时候,只需要使用- uploadTaskWithRequest:fromData:或- uploadTaskWithRequest:fromFile:之类的方法,其中参数的话只需要根提供数据(NSData)或者数据的磁盘位置(NSURL*fileURL)就可以构造出一个上传的session task了,简化了操作。

我们通过HTTP协议可以完成的操作都属于这三类任务之一。

NSURLSessionTask主要有三个任务操作方法,分别是:

NSURLSession一般使用方法:

#import "NetRequestViewController.h"

@interface NetRequestViewController ()<NSURLSessionDelegate,NSURLSessionDataDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate>

@property (nonatomic,strong) NSMutableData *dataM;

@property (nonatomic,strong) NSURLSession *session;

@end

static NSString *urlstr =@"http://api.yanagou.net/app/web.json";

static NSString *urlstr1 = @"http://api.yanagou.net/app/user/login.json";

static NSString *urlstrimage = @"http://120.25.226.186:32812/resources/images/minion_01.png";

static NSString *urlstrimage1 = @"http://120.25.226.186:32812/resources/images/minion_02.png";
@implementation NetRequestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    //一般的三种请求方式
    //GET In Block
    //[self setDataTask_get];
    
    //POST In Block
    //[self setDataTask_post];
    
    //代理方法 In delegate
    //[self setDataTask_delegate];
    
    
    //下载方法
    //下载1. In Block
    [self setDataTask_1];

    //下载2. In Block
    [self setDownloadTask_2];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    /*
     针对:setDataTask_delegate
     设置代理之后的强引用问题
     NSURLSession 对象在使用的时候,如果设置了代理,那么 session 会对代理对象保持一个强引用,在合适的时候应该主动进行释放
     可以在控制器调用 viewDidDisappear 方法的时候来进行处理,可以通过调用 invalidateAndCancel 方法或者是 finishTasksAndInvalidate 方法来释放对代理对象的强引用
     
     invalidateAndCancel 方法直接取消请求然后释放代理对象
     finishTasksAndInvalidate 方法等请求完成之后释放代理对象。
     */
    [self.session finishTasksAndInvalidate];

}

/*
        NSURLSessionDataTask 发送 GET 请求:
        发送 GET 请求的步骤非常简单,只需要两步就可以完成:
            1.使用 NSURLSession 对象创建 Task
            2.执行 Task
 
*/
- (void)setDataTask_get
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:urlstr];
    //创建 NSURLSession 对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    /**
     根据对象创建 Task 请求
     
     url  方法内部会自动将 URL 包装成一个请求对象(默认是 GET 请求)
     completionHandler  完成之后的回调(成功或失败)
     
     param data     返回的数据(响应体)
     param response 响应头
     param error    错误信息
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:
                                      ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                          
                                          //解析服务器返回的数据
//                                          NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
                                          NSLog(@"%@", response);

                                          NSDictionary *dic = [self dictionaryWithdata:data];
                                          NSLog(@"%@", dic);


                                          //默认在子线程中解析数据
                                          NSLog(@"%@", [NSThread currentThread]);
                                      }];
    //发送请求(执行Task)
    [dataTask resume];
}

/*
        NSURLSessionDataTask 发送 POST 请求
        发送 POST 请求的步骤与发送 GET 请求一样:
            1.使用 NSURLSession 对象创建 Task
            2.执行 Task
 */
- (void)setDataTask_post
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:urlstr1];
    //创建可变请求对象
    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
    //修改请求方法
    requestM.HTTPMethod = @"POST";
    //设置请求体
    requestM.HTTPBody = [@"username=name&password=000000&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    //创建会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    //创建请求 Task
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:
                                      ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                          
                                          //解析返回的数据
                                          NSDictionary *dic = [self dictionaryWithdata:data];
                                          NSLog(@"%@",dic);

                                      }];
    //发送请求
    [dataTask resume];
}

/*
            NSURLSessionDataTask 设置代理发送请求
            创建 NSURLSession 对象设置代理
                1.使用 NSURLSession 对象创建 Task
                2.执行 Task
 */

- (void)setDataTask_delegate
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:urlstr1];
    //创建可变请求对象
    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
    //设置请求方法
    requestM.HTTPMethod = @"POST";
    //设置请求体
    requestM.HTTPBody = [@"username=name&password=000000&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];

    //创建会话对象,设置代理
    /**
     第一个参数:配置信息
     第二个参数:设置代理
     第三个参数:队列,如果该参数传递nil 那么默认在子线程中执行
     */
    self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                          delegate:self delegateQueue:nil];
    //创建请求 Task
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:requestM];
    //发送请求
    [dataTask resume];
    
}
//遵守协议,实现代理方法(常用的有三种代理方法)

- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler
{
    //子线程中执行
    NSLog(@"接收到服务器响应的时候调用 -- %@", [NSThread currentThread]);
    
    self.dataM = [NSMutableData data];
    //默认情况下不接收数据
    //必须告诉系统是否接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    
    NSLog(@"接受到服务器返回数据的时候调用,可能被调用多次");
    //拼接服务器返回的数据
    [self.dataM appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    
    NSLog(@"请求完成或者是失败的时候调用");
    //解析服务器返回数据
    NSLog(@"%@", [self dictionaryWithdata:self.dataM]);
    
}

/*
    以下两个方法无法监听下载进度,如要获取下载进度,可以使用代理的方式进行下载。
 
    dataTask 和 downloadTask 下载对比:
 
    * NSURLSessionDataTask:
    1.下载文件可以实现离线断点下载,但是代码相对复杂.
 
    * NSURLSessionDownloadTask:
    1.下载文件可以实现断点下载,但不能离线断点下载
    2.内部已经完成了边接收数据边写入沙盒的操作
    3.解决了下载大文件时的内存飙升问题

*/

/*
    NSURLSessionDataTask 简单下载:
    在前面请求数据的时候就相当于一个简单的下载过程,NSURLSessionDataTask 下载文件具体的步骤与上类似:
        1.使用 NSURLSession 对象创建一个 Task 请求
        2.执行请求
 */

- (void)setDataTask_1
{
    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:
                                                    urlstrimage]
                                 completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                     
                                     //解析数据
                                     UIImage *image = [UIImage imageWithData:data];
                                     NSLog(@"%@",image);
                                     //回到主线程设置图片
                                     dispatch_async(dispatch_get_main_queue(), ^{
//                                         self.imageView.image = image;
                                     });
                                     
                                 }] resume];
}




/*
        NSURLSessionDownloadTask 简单下载:
        使用 NSURLSession 对象创建下载请求:
            1.在下载请求中移动文件到指定位置
            2.执行请求
*/

- (void)setDownloadTask_2
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:urlstrimage1];
    //创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //创建会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    //创建会话请求
    //优点:该方法内部已经完成了边接收数据边写沙盒的操作,解决了内存飙升的问题
    NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request
                                                        completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                                                            
                                                            //默认存储到临时文件夹 tmp 中,需要剪切文件到 cache
                                                            NSLog(@"%@", location);//目标位置
                                                            NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]
                                                                                  stringByAppendingPathComponent:response.suggestedFilename];
                                                            
                                                            /**
                                                             fileURLWithPath:有协议头
                                                             URLWithString:无协议头
                                                             */
                                                            [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
                                                            
                                                        }];
    //发送请求
    [downTask resume];
}

/*
 上传文件操作:
 使用 NSURLSession 进行上传文件操作,有些麻烦,如果嫌麻烦,也可以使用AFN框架就好。
 
 使用 NSURLSession 上传文件主要步骤及注意点
 主要步骤:
 1.确定上传请求的路径( NSURL )
 2.创建可变的请求对象( NSMutableURLRequest )
 3.修改请求方法为 POST
 4.设置请求头信息(告知服务器端这是一个文件上传请求)
 5.按照固定的格式拼接要上传的文件等参数
 6.根据请求对象创建会话对象( NSURLSession 对象)
 7.根据 session 对象来创建一个 uploadTask 上传请求任务
 8.执行该上传请求任务(调用 resume 方法)
 9.得到服务器返回的数据,解析数据(上传成功 | 上传失败)
 
 注意点:
 1.创建可变的请求对象,因为需要修改请求方法为 POST,设置请求头信息
 2.设置请求头这个步骤可能会被遗漏
 3.要处理上传参数的时候,一定要按照固定的格式来进行拼接
 4.需要采用合适的方法来获得上传文件的二进制数据类型( MIMEType,获取方式如下)
 对着该文件发送一个网络请求,接收到该请求响应的时候,可以通过响应头信息中的 MIMEType 属性得到
 使用通用的二进制数据类型表示任意的二进制数据 application/octet-stream
 调用 C 语言的 API 来获取
 [self mimeTypeForFileAtPath:@"此处为上传文件的路径"]
 */

/*!
 * @brief 把格式化的JSON格式的字符串转换成字典
 * @return 返回字典
 */
- (NSDictionary *)dictionaryWithdata:(NSData *)data {
    
    NSError *err;
    NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data
                                                        options:NSJSONReadingMutableContainers
                                                          error:&err];
    
    if(err) {
        NSLog(@"json解析失败:%@",err);
        return nil;
    }
    return dic;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
@end

3.AFNetworking
AFNetworking是基于URL加载系统的网络框架,AFNetworking 1.0建立在NSURLConnection的基础API之上 ,AFNetworking 2.0开始使用NSURLConnection的基础API ,以及较新基于NSURLSession的API的选项。 AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。
我们重点探讨AFURLSessionManager和AFHTTPSessionManager两个类,因为它们都是基于NSURLSession的,前者的用法可以在官方文档上找到,而且用起来稍显麻烦,AFHTTPSessionManager的用法如下所示。

下面的代码演示如何向服务器发送获取数据的GET请求

    // 创建HTTP会话管理器对象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // AFNetworking默认接受的MIME类型是application/json
    // 有些服务器虽然返回JSON格式的数据但MIME类型设置的是text/html
    // 通过下面的代码可以指定支持的MIME类型有哪些
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:
        @"application/json", @"text/html", nil];
    // 向服务器发送GET请求获取JSON数据
    [manager
        // 统一资源定位符
        GET:@""
        // 请求参数
        parameters:@{  }
        // 当完成进度变化时回调的Block
        progress:nil
        // 服务器响应成功要回调的Block
        success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        }
        // 服务器响应失败要回调的Block
        failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        }
    ];

下面的代码演示了如何向服务器发送上传数据的POST请求

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager
        // 统一资源定位符
        POST:@""
        // 请求参数
        parameters:@{ }
        // 构造请求报文消息体的Block
        constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
            // 可以调用appendPartWithFileData:name:fileName:mimeType:等方法
            // 将上传给服务器的数据放到请求报文的消息体中
        }
        // 当上传进度变化时回调的Block
        progress:^(NSProgress * _Nonnull uploadProgress) {

        }
        // 服务器响应成功要回调的Block
        success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {    
        } 
        // 服务器响应失败要回调的Block
        failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        }
    ];

AFNetworking还封装了判断网络可达性的功能,使用该功能的代码如下所示:

    // 创建网络可达性管理器
    AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager manager];
    // 设置当网络状况发生变化时要回调的Block
    [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        switch (status) {
            case AFNetworkReachabilityStatusNotReachable:
                NSLog(@"没有网络连接");
                break;
            case AFNetworkReachabilityStatusReachableViaWiFi:
                NSLog(@"使用Wi-Fi");
                break;
            case AFNetworkReachabilityStatusReachableViaWWAN:
                NSLog(@"使用移动蜂窝网络");
                break;
            default:
                break;
        }
    }];
    // 开始监控网络状况变换
    [manager startMonitoring];

补充
1. 断点续传的实现原理
HTTP协议设置请求头内容,支持只请求某个资源的某一部分。
Range 请求的资源范围;
Content-Range 响应的资源范围;
在连接断开重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源,来实现断点续传。
分块请求资源实例:
Eg1:Range: bytes=306302- :请求这个资源从306302个字节到末尾的部分;
Eg2:Content-Range: bytes 306302-604047/604048:响应中指示携带的是该资源的第306302-604047的字节,该资源共604048个字节;
客户端通过并发的请求相同资源的不同片段,来实现对某个资源的并发分块下载。从而达到快速下载的目的。目前流行的FlashGet和迅雷基本都是这个原理。
2. 多线程下载的原理
下载工具开启多个发出HTTP请求的线程,每个http请求只请求资源文件的一部分,例如:Content-Range: bytes 20000-40000/47000;
最后合并每个线程下载的文件。

5. 数据解析

通过HTTP从服务器获得的数据通常都是JSON格式或XML格式的,下面对这两种数据格式做一个简单的介绍。
1.JSON
JSON全称JavaScript对象表达式(JavaScript Object Notation),是目前最流行的存储和交换文本信息的语法,和XML相比,它更小、更快,更易解析,是一种轻量级的文本数据交换格式。

JSON的语法规则可以简单的总结成以下几条:

  1. 数据由逗号分隔;
  2. 花括号保存对象;
  3. 方括号保存数组。

JSON中的值可以是:

- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在方括号中)
- 对象(在花括号中)
- null

不难看出,JSON用键值对的方式描述了对象,它的形态跟Objective-C的NSDictionary类型是完全一致的,可以通过NSJSONSerialization类的两个类方法实现JSON数据和字典或数组之间的相互转换。

// 将数据转换成对象(通常是数组或字典)
+ (id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
// 将数组或字典装换成JSON数据
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error

通过服务器获得JSON数据后,最终需要将它转换成我们程序中的对象。事实上,将JSON转换成模型对象的操作在开发网络应用中是很常见的,我们可以使用KVC(Key-Value Coding)的方式将一个字典赋值给一个对象的属性。

说明:KVC通常翻译为键值编码,它允许开发者通过名字访问对象属性,而无需调用明确的存取方法,这样就可以实现在运行时而不是在编译时确定属性的绑定。这种间接访问能让代码变得更灵活和更具复用性。

对于对象中关联了其他对象或者对象的属性跟字典中的键不完全匹配的场景,KVC就显得不那么方便了,但是已经有很多优秀的第三方库帮助我们实现了JSON和模型对象的双向转换,比如JSONModel、MJExtension和YYModel。

说明:YYModel> MJExtension> JSONModel(侵入性:模型类必须继承JSONModel)

XML
XML全称可扩展标记语言,被设计用来传输和存储数据。在JSON被广泛使用之前,XML是异构系统之间交换数据的事实标准,它是一种具有自我描述能力的传输数据的标记语言,如下所示。

<?xml version="1.0" encoding="ISO-8859-1"?>
<note>
    <to>Tove</to>
    <from>Jani</from>
    <heading>Reminder</heading>
    <body>Don't forget me this weekend!</body>
</note>

因为不常用XML解析,所以其他的就不一一介绍了。

6. 网络优化
  1. 数据格式优化,减少数据传输量和序列化时间
  2. 引入重试机制,提升网络服务成功率
  3. 弱网和网络抖动优化
    引入了网络质量参数,通过网络类型(网络类型切换时,例如WIFI和移动网络、4G/3G切换至2G时,)和端到端Ping值进行计算,根据不同的网络质量改变网络服务策略

以上是iOS开发中跟网络相关的知识,但是还远不止这些,例如:如何通过证书保证网络通信的安全,如何有效的使用缓存来提升性能和减少网络开销以及URL缓存的过期模型和验证模型等。都需要我们深入学习研究!

上一篇下一篇

猜你喜欢

热点阅读