iOS_将网络数据存储到内存
翻译原文链接
通过从URL会话创建数据任务将获取到的数据直接存入内存
概述
对于比较小的一些和远程服务器的交互,你可以通过使用URLSessionDataTask来获取数据并存入内存(而不是使用URLSessionDownloadTask类,用来将数据直接存入文件系统)。一个数据任务通常被认为使用来对网络服务器进行请求。
你是用一个URL会话实体来创建一个任务。如果你的需求相当的简单,你可以使用URLSession类的shared实体。如果你想通过代理回调来和数据转换进行交互的话,你将需要创建一个会话而不是使用shared实体。当创建一个会话时你可以使用URLSessionConfiguration实例,同时传入一个实现了URLSessionDelegate或其子协议的类达到该目的。会话可以被重新使用来创建多个任务,所以,你可以对每一个不同的配置需求,创建一个会话并保存起来。
注意
注意不要创建比你所需要更多的会话。举个例子,如果你的app不同部分需要使用类似配置的会话,那就创建一个会话并共享
一旦你有了一个会话,你就可以通过dataTask()
方法来创建一个任务。任务在创建的时候是处于挂起的状态,可以通过resume()方法来开启任务。
通过Completion Handler来接收结果
获取数据最简单的方法是创建一个带有completion handler的数据任务。按照这个约定,这个任务将会在你所提供的completion handler中传递服务器的回应,数据和可能出现的错误。下图展示了一个会话和一个任务之间的关系以及结果是如何传递给completion handler的。
figure_1要创建一个带有completion handler的数据任务,要调用URLSession
的dataTask(with:)的方法。你的completion handler需要做以下三件事:
- 验证
error
参数是nil
。如果不是的话,将会发生传输错误;就要处理错误并退出。 - 检查
response
参数来验证状态码是成功而且MIME类型是期待的类型。如果不是这样的话,就要处理服务器错误并退出。 - 按需使用
data
实例。
以下代码展示了获取URL内容的startLoad()
方法。一开始,它通过URLSession
类的shared实例来创建数据任务并将结果传递给completion handler。再检查完本地和服务器错误后,这个handler将数据转换成字符串,并用它来填充WKWebView控件,当然,你的app可能将获取到的数据用于其他用处,像它转换成一个数据模型。
func startLoad() {
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClient(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
self.webView.loadHTMLString(string, baseURL: url)
}
}
}
task.resume()
}
重点
Completion Handler是在另一个GCD队列而不是创建任务的队列中被调用。所以,任何使用
data
和error
来更新界面的工作--像更新webview--应该明确被放到主线程来做就像这里处理的一样。
通过代理来接收传递的详细信息和结果
当创建数据任务时,想要更进一步获得数据任务执行时的活动状态,你可以对session设置一个代理,而不是comppletion handler。如下图:
figure_2通过这种方式,数据部分将通过URLSessionDelegate的方法urlSession(:dataTask:didReceive:)在他们返回时提供,直到传输结束或者传输失败时终止。在传输过程中,代理也会接受其他种类的事件。
当你想使用代理这种方法的时候你要创建你自己的URLSession实例,而不是使用URLSession
类的简单的shared
实例。创建一个新的session将允许你把session的代理设为你自己的类。代码如下。
声明并将自己创建的类实现一个或多个代理协议(URLSessionDelegate,URLSessionTaskDelegate,URLSessionDataDelegate和URLSessionDownloadDelegate)。然后通过初始化方法init(configuration:delegate:delegateQueue:)来创建URLSession
实例。你可以通过这个初始化方法来自定义配置该实例。将waitsForConnectivity设为true
是个很好的例子,这样的话,会话就会等待到合适的连接,而不是在需要的连接不可用时直接失败。
private lazy var session: URLSession = {
let configuration = URLConfiguration.default
configuration.waitsForConnectivity = true
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
接下来一段代码将展示startLoad()
方法,它使用上面的这个会话来开启一个数据任务,并且使用代理回调来处理接受数据和错误。这段代码会实现三个代理回调:
-
urlSession(_:dataTask:didReceive:completionHandler:)用来验证服务器回应带有成功的HTTP状态码,并且MIME类型为
text/html
或者text/plain
,如果两者都不是,这个任务将会被取消;反之,它将继续执行。 -
urlSession(_:dataTask:didReceive:)将接受任务中的每个数据块并将它拼接到叫做
receivedData
的这个缓冲区中。 -
urlSession(_:task:didCompleteWithError:)首先查看是否发生了传输层的错误。如果没有,它将试图将
receivedData
缓冲块转换为字符串并将它设置为webView
的内容。
var receivedData: Data?
func startLoad() {
loadButton.isEnabled = false
let url = URL(string: "http://www.example.com/")!
receivedData = Data()
let task = session.dataTask(with: url)
task.resume()
}
// delegate methods
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode),
let mimeType = response.mimeType,
mimeType == "text/html" else {
completionHandler(.cancel)
return
}
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.receivedData?.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
DispatchQueue.main.async {
self.loadButton.isEnabled = true
if let error = error {
handleClientError(error)
} else if let receivedData = self.receivedData,
let string = String(data: receivedData, encoding: .utf8) {
self.webView.loadHTMLString(string, baseURL: task.currentResponse?.url)
}
}
}
代理协议所提供的内容不仅限于以上代码,例如处理信任签名,重定向,和其他的情况。在URLSession的讨论中,再Using a URL Session
中描述了在传输过程中可能发生的各种回调。
更多可参看
- URLSession 一个协调一组网络传输任务相关的类
- URLSessionTask 一个任务,像下载一个特殊的资源,执行一个URL会话