URLSession 系列第二篇:手把手教你封装超实用的网络工具类

400 阅读6分钟

这里每天分享一个 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) { dataresponseerror in
            // 处理请求完成后的结果
            if let error = error {
                completion(.failure(error))
                return
            }
            
            // 判断响应是否是 HTTP 响应
            guard let httpResponse = response asHTTPURLResponse,
                  (200...299).contains(httpResponse.statusCode) else {
                    // 判断状态码是否在 200-299 之间,这之间表示请求成功,否则抛出错误
                let statusError = NSError(domain"com.networking.error"code: -1userInfo: [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: -1userInfo: [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 NetworkErrorError {
    case unexpectedResponse // 未知响应
    case invalidJSON // 无效的 JSON 数据
    case serverError(statusCode: Int// 服务器错误
    case unknown(Error// 未知错误
}

extension NetworkManager {
    func request(_ requestURLRequestcompletion@escaping (Result<DataNetworkError>) -> 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(urlURLcompletion@escaping (Result<[StringAny], NetworkError>) -> Void) {
        get(url: url) { result in
            switch result {
            case .success(let data):
                do {
                    if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [StringAny] {
                        completion(.success(json))
                    } else {
                        completion(.failure(.invalidJSON))
                    }
                } catch {
                    completion(.failure(.unexpectedResponse))
                }
            case .failure(let error):
                completion(.failure(.unknown(error)))
            }
        }
    }
}

使用示例

现在,把上边所有代码拼在一起,我们就成功用 URLSession 封装好了一个网络请求工具类,接下来我们来看一下如何在项目中使用这个工具类。

GET 请求

import UIKit

class ViewControllerUIViewController {
    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 ViewControllerUIViewController {
    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新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!