这里每天分享一个 iOS 的新知识,快来关注我吧
前言
上一篇文章讲了 URLSession 的基本概念和使用方法,这一篇文章将继续实践 URLSession 的用法,手把手教你封装一个网络请求工具类。
在看这篇文章之前,推荐你先看一下上一篇文章,这样会更容易理解这篇文章的内容。
URLSession 系列第一篇:iOS 开发者必须掌握的 URLSession 使用指南
创建网络库的基本结构
首先,我们需要创建一个新的 Swift 文件,暂且命名为 NetworkManager.swift
,你也可以使用其他名字。在这个文件中,我们将定义一个 NetworkManager
类,用于封装所有的网络请求逻辑。
import Foundation
class NetworkManager {
// 创建一个单例 NetworkManager 对象
static let shared = NetworkManager()
// 防止其他对象创建 NetworkManager 对象,所以将 init() 方法私有化
private init() {}
// 创建一个共享的 URLSession 对象
private let session = URLSession.shared
}
在这个文件中,我们定义了一个 NetworkManager
类,其中包含了一个 shared
单例对象,一个私有的 init()
初始化方法,一个私有的 session
属性。
定义网络请求方法
接下来,我们在 NetworkManager
类中定义一个 request()
方法,用于发送网络请求。
extension NetworkManager {
// 创建一个 request() 方法,用于发送网络请求
// 参数 completion: @escaping (Result<Data, Error>) -> Void,请求完成后的回调
func request(_ request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) {
// 创建一个网络请求任务
let task = session.dataTask(with: request) { data, response, error in
// 处理请求完成后的结果
if let error = error {
completion(.failure(error))
return
}
// 判断响应是否是 HTTP 响应
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
// 判断状态码是否在 200-299 之间,这之间表示请求成功,否则抛出错误
let statusError = NSError(domain: "com.networking.error", code: -1, userInfo: [NSLocalizedDescriptionKey: "Unexpected response"])
completion(.failure(statusError))
return
}
// 判断返回的数据是否存在,如果存在则调用 completion 回调
if let data = data {
completion(.success(data))
}
}
// 开始执行网络请求任务
task.resume()
}
}
request()
方法用于发送网络请求,接收一个 URLRequest
对象和一个回调闭包作为参数。在方法中,我们创建了一个网络请求任务,然后执行这个任务,当任务完成后,我们会判断请求是否成功,然后调用回调闭包。
实现 GET 请求
接下来,我们在 NetworkManager
中添加一个 GET 请求的方,创建 URLRequest
之后,调用 request()
方法发送网络请求。这里我们使用了一个扩展方法,将 GET 请求的逻辑封装在这个方法中,方便调用
extension NetworkManager {
// 创建一个 get() 方法,用于发送 GET 请求
// 参数 url: URL,请求的 URL 地址
// 参数 completion: @escaping (Result<Data, Error>) -> Void,请求完成后的回调
func get(url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
var request = URLRequest(url: url)
// 设置请求的方法
request.httpMethod = "GET"
self.request(request, completion: completion)
}
}
实现 POST 请求
再接下来,我们在 NetworkManager
中添加一个 POST 请求的方法,和 GET 请求类似。
extension NetworkManager {
// 创建一个 post() 方法,用于发送 POST 请求
// 参数 url: URL,请求的 URL 地址
// 参数 body: Data?,请求的 body 数据
// 参数 completion: @escaping (Result<Data, Error>) -> Void,请求完成后的回调
func post(url: URL, body: Data?, completion: @escaping (Result<Data, Error>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = body
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
self.request(request, completion: completion)
}
}
处理 JSON 数据
大部分的 API 接口请求返回的数据都是 JSON 格式的,所以我们这里也以 JSON 为例,为了处理 JSON 数据,我们可以创建一个通用的方法,将 Data 转换为 JSON 对象。
extension NetworkManager {
func getJSON(url: URL, completion: @escaping (Result<[String: Any], Error>) -> Void) {
get(url: url) { result in
switch result {
case .success(let data):
// 将 Data 转换为 JSON 对象
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
// 转换成功,调用 completion 回调,将 JSON 对象传递出去
completion(.success(json))
} else {
// 如果转换失败,则抛出错误
let jsonError = NSError(domain: "com.networking.error", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON"])
completion(.failure(jsonError))
}
} catch let error {
// 如果解析过程失败,则抛出错误
completion(.failure(error))
}
case .failure(let error):
// 如果请求失败,则直接调用 completion 回调,将错误传递出去
completion(.failure(error))
}
}
}
}
错误处理
在网络请求中,会有很多原因导致请求失败,那么错误处理就是非常重要的一环。我们已经在上面的代码中添加了一些基本的错误处理逻辑。下面是一个更详细的错误处理示例。
我们定义一个 NetworkError
枚举来表示网络请求中可能出现的错误,然后在 request()
、getJSON()
方法中把错误类型替换成我们自己的类型即可。
// 创建一个 NetworkError 枚举,用于表示网络请求中可能出现的错误
enum NetworkError: Error {
case unexpectedResponse // 未知响应
case invalidJSON // 无效的 JSON 数据
case serverError(statusCode: Int) // 服务器错误
case unknown(Error) // 未知错误
}
extension NetworkManager {
func request(_ request: URLRequest, completion: @escaping (Result<Data, NetworkError>) -> Void) {
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(.unknown(error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.unexpectedResponse))
return
}
guard (200...299).contains(httpResponse.statusCode) else {
completion(.failure(.serverError(statusCode: httpResponse.statusCode)))
return
}
if let data = data {
completion(.success(data))
} else {
completion(.failure(.unexpectedResponse))
}
}
task.resume()
}
func getJSON(url: URL, completion: @escaping (Result<[String: Any], NetworkError>) -> Void) {
get(url: url) { result in
switch result {
case .success(let data):
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
completion(.success(json))
} else {
completion(.failure(.invalidJSON))
}
} catch {
completion(.failure(.unexpectedResponse))
}
case .failure(let error):
completion(.failure(.unknown(error)))
}
}
}
}
使用示例
现在,把上边所有代码拼在一起,我们就成功用 URLSession 封装好了一个网络请求工具类,接下来我们来看一下如何在项目中使用这个工具类。
GET 请求
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
NetworkManager.shared.getJSON(url: url) { result in
switch result {
case .success(let json):
print("JSON: \(json)")
case .failure(let error):
print("Error: \(error)")
}
}
}
}
POST 请求
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let body = """
{
"title": "标题",
"body": "这是一个新的帖子",
"userId": 1
}
""".data(using: .utf8)
NetworkManager.shared.post(url: url, body: body) { result in
switch result {
case .success(let data):
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
}
case .failure(let error):
print("Error: \(error)")
}
}
}
}
总结
这篇文章是 URLSession 系列的第二篇,我们通过实践,手把手教你封装一个网络请求工具类。在这个工具类中,我们封装了 GET 请求、POST 请求、JSON 数据处理、错误处理等功能。这个工具类可以帮助你更方便地发送网络请求,处理返回的数据,以及处理请求中的错误。
通过封装,我们可以更方便地进行网络请求,并且可以在项目中复用这段代码。希望这篇文章对你有所帮助!
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!