iOS网络编程 - NSURLConnection

在iOS7后,NSURLSession基本代替了NSURLConnection进行网络开发,在iOS9后,NSURLConnection相关方法被完全的弃用,iOS系统有向下兼容的特性,尽管NSURLConnection已经被弃用,但在开发中,其方法依然可以被使用,并且如果需要兼容到很低版本的iOS系统,有时就必须使用NSURLConnection类了。

使用NSURLConnection用到的六个关于网络连接的类分别是:

  • NSURLConnection:提供了初始化、开始和取消一个网络连接
  • NSURL:封装了一个网络路径
  • NSURLRequest:设置网络的请求信息
  • NSMutableURLRequest:NSURLRequest的子类,可以设置请求参数
  • NSURLResponse:接收服务器返回的信息
  • NSError:包含了网络请求中遇到的错误信息

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

  1. 创建一个NSURL对象,设置请求路径(设置请求路径)
  2. 传入NSURL创建一个NSURLRequest对象,设置请求头和请求体(创建请求对象)
  3. 使用NSURLConnection发送NSURLRequest(发送请求)

使用NSURLConnection发送请求有两张情况,同步请求和异步请求。

NSURLConnection 同步请求

对于网络请求分为同步和异步两种,同步是指在请求结果返回之前,程序代码会卡在请求处,之后的代码不会被执行,异步是指在发送请求之后,一边在子线程中接收返回数据,一边执行之后的代码,当返回数据接收完毕后,采用回调的方式通知主线程做处理。

异步的连接将会创建一个新的线程,这个线程将会来负责下载的动作。而对于同步连接,在下载连接和处理通讯时,则会阻塞当前调用线程。许多开发者都会认为同步的连接将会堵塞主线程,其实这种观点是错误的。

一个同步的连接是会阻塞调用它的线程,如果你在主线程中创建一个同步连接,主线程会阻塞,但是如果你并不是从主线程开启的一个同步的连接,它将会类似异步的连接一样,因此这种情况并不会堵塞你的主线程。

事实上,同步和异步的主要区别就是运行 runtime 为会异步连接创建一个线程,而同步连接则不会。

下面是同步请求的示例:

1
2
3
4
5
6
7
8
9
let request = NSMutableURLRequest(URL: NSURL(string: url!)!)
do {
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
print(data)
}
catch {
print("出错了")
}

NSURLConnection 异步请求

使用同步的方式进行请求有一个很大的弊端,在进行网络请求时,数据的返回往往需要一定时间,不可能瞬间完成,使用同步的方式将很有可能会导致界面卡死,没有提示也不能交互任何用户操作,会给用户程序卡死的假象。

NSURLConnection类提供代理和block两种方式进行异步请求操作。

NSURLConnection 代理

首先遵守NSURLConnectionDataDelegate协议:

1
2
3
class ViewController: UIViewController, NSURLConnectionDataDelegate {
var url: String
}

调用如下的代码进行请求:

1
2
3
func connectionWithDelegate(url: String?) -> NSURLConnection {
return NSURLConnection(request: NSMutableURLRequest(URL: NSURL(string: url!)!), delegate: self)!
}

请求发出后,会一次调用如下代理方法进行请求过程的监听和数据的获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func connection(connection: NSURLConnection, didFailWithError error: NSError) {
print("连接失败,\(error)")
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
print("收到服务器响应,\(response)")
}
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
data.appendData(data)
print("正在接收数据")
}
func connectionDidFinishLoading(connection: NSURLConnection) {
print("数据传输完成,\(connection)")
}

其中didReceiveData有可能被多次调用,比如下载一个文件夹,文件夹中每有一个文件下载完,有可能就会调用 一次,所以这个函数有时会使用累加代码,将多次下载的数据写入到一个文件里面。

NSURLConnection Block

使用如下代码进行block方式的异步请求,在block中会传入请求到的返回数据和数据信息等参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func connectionWithThread(url: String?) {
let queue = NSOperationQueue()
let request = NSMutableURLRequest(URL: NSURL(string: url!)!)
NSURLConnection.sendAsynchronousRequest(request, queue: queue) {
(resp: NSURLResponse?, data: NSData?, error: NSError?) in
if error != nil {
print("出错了,\(error)")
}
else {
let readingOprions = NSJSONReadingOptions.MutableContainers
let dict = try? NSJSONSerialization.JSONObjectWithData(data!, options: readingOprions)
self.imageUrl = dict?.objectForKey("headimgurl") as? String
}
}
}

NSURLConnection 网络请求设置

网络请求的设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func setRequestMessage(url: String?) {
let user = "username=admin&password=12345&department=125"
let queue = NSOperationQueue()
let request = NSMutableURLRequest(URL: NSURL(),
cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData,
timeoutInterval: 60) //超出60秒后请求失败
request.URL = NSURL(string: url!)!
request.HTTPMethod = "POST"
let data = user.dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = data
NSURLConnection.sendAsynchronousRequest(request, queue: queue) {
(resp: NSURLResponse?, data: NSData?, error: NSError?) in
if error != nil {
print("出错了,\(error)")
}
else {
print("服务器返回了响应消息,\(resp)")
}
}
}

其中的核心在于:

1
2
3
let request = NSMutableURLRequest(URL: NSURL(),
cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData,
timeoutInterval: 60) //超出60秒后请求失败

NSURLRequestCachePolicy指定缓存逻辑。URL加载系统提供了一个磁盘和内存混合的缓存,来响应网络请求。这个缓存允许一个应用减少对网络连接的依赖,并且增加性能。

使用缓存的目的是为了使用的应用程序能更快速的响应用户输入,是程序高效的运行。有时候我们需要将远程web服务器获取的数据缓存起来,减少对同一个url多次请求。

可变请求才具有cachePolicy和timeoutInterval参数。

NSURLRequestCachePolicy 参数

  • NSURLRequestUseProtocolCachePolicy = 0:

默认缓存策略。

如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取;

如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定;

如果它指定内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化;

假如内容没有变化,那么响应就从本地缓存返回数据;

如果内容变化了,那么数据将从源端获取;

  • NSURLRequestReloadIgnoringLocalCacheData = 1:

URL应该加载源端数据,不使用本地缓存数据;

  • NSURLRequestReturnCacheDataElseLoad = 2:

指定已存的缓存数据应该用来响应请求,不管它的生命时长和过期时间;

如果在缓存中没有已存数据来响应请求的话,数据从源端加载;

  • NSURLRequestReturnCacheDataDontLoad = 3:

指定已存的缓存数据用来满足请求,不管生命时长和过期时间;

如果在缓存中没有已存数据来响应URL加载请求的话,不去尝试从源段加载数据, 此时认为加载请求失败;

这个常量指定了一个类似于离线模式的行为,常用于离线模式;

  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4:

本地缓存数据、代理和其他中介都要忽视他们的缓存,直接加载源数据;

NSURLRequestReloadIgnoringCacheData和NSURLRequestReloadIgnoringLocalCacheData,两个的设置相同;

  • NSURLRequestReloadRevalidatingCacheData = 5:

指定如果已存的缓存数据被提供它的源段确认为有效则允许使用缓存数据响应请求,否则从源段加载数据;

只有响应http和https的请求会被缓存,ftp和文件协议当被缓存策略允许的时候尝试接入源段;

自定义的NSURLProtocol类能够保护缓存, 如果它们被选择使用的话;

HTTPMethodHTTPBody分别对应网络请求中的请求头和请求体:

1
2
3
request.HTTPMethod = "POST"
let data = user.dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = data