URLSession实例是应用程序执行的请求的管理器或协调器。请求被称为任务,URLSessionTask的一个实例。您永远不会直接使用URLSessionTask类。Foundation定义了许多URLSessionTask子类。每个子类都有一个特定的目标,例如下载或上传数据。
选项1:完成处理程序
创建数据任务
让我们使用URLSession API来执行HTTP请求。目标是获取图像的数据。在开始之前,我们需要定义远程映像的URL。
下一步是创建一个数据任务,即URLSessionDataTask类的实例。数据任务始终与URLSession实例相关联。为了简单起见,我们通过其共享类属性向URLSession类请求共享URLSession实例(单例)。
URLSession.shared
然后,我们要求共享URLSession实例通过调用dataTask(with:completionHandler:)方法创建数据任务。此方法返回一个URLSessionDataTask实例,并接受两个参数,一个URL对象和一个完成处理程序。完成处理程序是一个闭包,在数据任务成功或失败完成时执行。完成处理程序接受三个参数,可选的Data对象、可选的URLResponse对象和可选的Error对象。
import UIKit
let url = URL(string: "https://bit.ly/2LMtByx")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
}
数据任务失败或成功。如果数据任务失败,则错误有一个值。如果数据任务成功,则数据和响应具有值。我们目前对URLResponse对象不感兴趣。我们安全地解压缩Data对象并使用它来创建UIImage实例。如果data等于nil,则HTTP请求失败,我们将错误值打印到控制台。
import UIKit
let url = URL(string: "https://bit.ly/2LMtByx")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let image = UIImage(data: data)
} else if let error = error {
print("HTTP Request Failed \(error)")
}}
即使我们创建了一个数据任务,HTTP请求也不会被执行。我们需要对URLSessionDataTask实例调用resume()来执行它。
import UIKit
let url = URL(string: "https://bit.ly/2LMtByx")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let image = UIImage(data: data)
} else if let error = error {
print("HTTP Request Failed \(error)")
}}
task.resume()
如果您运行游乐场的内容并等待几秒钟,您应该会看到HTTP请求的结果出现在右侧的结果面板中。UIImage实例的尺寸显示在结果面板中。控制台上没有打印错误,这意味着HTTP请求已成功执行。
创建请求
我们通过将URL对象传递给dataTask(with:completionHandler:)方法来创建数据任务。这工作得很好,但它没有提供太多的灵活性。URLSession类定义了另一个接受URLRequest对象的方法。顾名思义,URLRequest结构封装了URL会话执行HTTP请求所需的信息。让我给你演示一下这是如何工作的。
我们通过将URL对象传递给初始化器来创建URLRequest对象,并将结果存储在一个名为request的变量中。我们稍后修改URLRequest对象,因此使用var而不是let。
var request = URLRequest(url: url)
我们将URLRequest对象传递给dataTask(with:completionHandler:)方法。该方法的名称与我们之前使用的方法相似。区别在于它接受URLRequest对象而不是URL对象。
import UIKit
let url = URL(string: "https://bit.ly/2LMtByx")!
var request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
let image = UIImage(data: data)
} else if let error = error {
print("HTTP Request Failed \(error)")
}}
task.resume()
执行操场上的内容。请注意,结果是相同的。您可能想知道我们使用URLRequest能得到什么。URLRequest对象允许我们配置URL会话执行的HTTP请求。我们可以通过httpMethod属性设置HTTP方法。在这个例子中,这是不必要的,因为它默认为GET。
request.httpMethod = "GET"
我们还可以定义请求的HTTP标头字段。如果需要覆盖默认的HTTP标头字段,则设置allHTTPHeaderFields属性,即[String:String]类型的字典。如果您需要处理身份验证,这一点也很重要。在本例中,我们添加了一个HTTP头字段,以便将API密钥传递给正在通信的服务器。
request.allHTTPHeaderFields = [
"X-API-Key": "123456789"]
您还可以通过调用setValue(_:forHTTPHeaderField:)方法来设置请求的HTTP标头字段。这是一个可变方法,用于设置给定HTTP标头字段的值。
request.setValue("application/png", forHTTPHeaderField: "Content-Type")
使用URLSession请求JSON
获取JSON是另一个常见的例子。这并不比获取远程图像的数据更困难。我们更新URL对象,并将Content-Type HTTP标头字段的值设置为application/json。
import UIKit
let url = URL(string: "https://bit.ly/3sspdFO")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
我们有几个选项来解码响应。除非你有充分的理由,否则我不建议使用JSONSerialization类。首选和推荐的方法是可解码协议与JSONDecoder类相结合。
我们定义了一个名为Book的结构体,它符合可解码协议。Book结构定义了两个属性,String类型的title和String类型的author。
import UIKit
struct Book: Decodable {
let title: String
let author: String
}
let url = URL(string: "https://bit.ly/3sspdFO")!
var request = URLRequest(url: url)
request.setValue(
"application/json",
forHTTPHeaderField: "Content-Type")
在完成处理程序中,我们创建了一个JSONDecoder实例并调用decode(_:from:)方法,从提供的JSON对象传递要解码的值的类型,并将要解码的JSON对象作为Data对象传递。我们将结果打印到控制台。执行游乐场的内容以查看结果。
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let books = try? JSONDecoder().decode([Book].self, from: data) {
print(books)
} else {
print("Invalid Response")
}
} else if let error = error {
print("HTTP Request Failed \(error)")
}}
task.resume()
选项2:Swift并发
当请求成功或失败完成时,我们传递给dataTask(with:completionHandler:)方法的完成处理程序会被调用。这很好,但有一个更优雅、更现代的API可以利用Swift并发。
执行HTTP请求是一个异步操作,我们在Task中执行。在Task的主体中,我们调用共享URLSession实例上的data(for:)方法。data(for:)方法接受URLRequest对象。这是一个through方法,因此我们将其封装在do-catch语句中。我们在方法调用前加上wait关键字,因为该方法是异步的。
Task {
do {
let (data, _) = try await URLSession.shared.data(for: request)
} catch {
print("Failed to Send POST Request \(error)")
}}
data(for:)方法返回一个包含两个值的元组,一个data对象和一个URLResponse对象。我们只对Data对象感兴趣。如前所述,我们在JSONDecoder实例的帮助下将Data对象转换为Book对象数组。
Task {
do {
let (data, _) = try await URLSession.shared.data(for: request)
if let books = try? JSONDecoder().decode([Book].self, from: data) {
print(books)
} else {
print("Invalid Response")
}
} catch {
print("Failed to Send POST Request \(error)")
}}
Swift并发有几个有趣的好处。首先,您可以从上到下阅读我们编写的代码。这使得代码更容易理解和推理。其次,错误处理内置于Swift并发中。我们不需要打开可选的Error对象来判断请求是否成功。如果请求失败,则抛出错误。简单。正确的第三,元组的值不是可选的。如果GET请求成功,那么我们保证可以访问Data对象和URLResponse对象。
选项3:使用Combine的响应式编程
URLSession API与苹果的反应式框架Combine巧妙地集成在一起。我们可以向发出GET请求响应的发布者询问共享URLSession实例。如果请求失败,则发布者终止或以错误完成。
URLSession.shared.dataTaskPublisher(for: request)
我们通过调用sink(receiveCompletion:receiveValue:)方法来订阅dataTaskPublisher(for:)方法返回的发布者。该方法接受两个参数,一个是发布者完成时调用的完成处理程序,另一个是请求响应可用时调用的值处理程序。我们可以通过值处理程序访问Data和URLResponse对象。
let cancellable = URLSession.shared.dataTaskPublisher(for: request)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
()
case .failure(let error):
print("Failed to Send GET Request \(error)")
}
}, receiveValue: { data, _ in
if let books = try? JSONDecoder().decode([Book].self, from: data) {
print(books)
} else {
print("Invalid Response")
}
})
sink(receiveCompletion:receiveValue:)方法返回AnyCancelable。我们需要保留AnyCancelable,直到请求完成。因此,我们将对AnyCancelable的引用存储在一个名为cancelable的常量中。
有待深入
在本教程中,我们研究了三个API,以使用URLSession API在Swift中执行HTTP请求。我希望你同意这不是火箭科学。URLSession API易于使用,如果您了解更多关于API的信息,它将灵活而强大。我们在本教程中只触及了表面。如果您想了解如何使用URLSession API执行PUT或POST请求,请参阅Swift中的How to Make a POST Request。