如何将网络请求封装得更容易扩展
import Foundation
struct User {
let name: String
let message: String
/// 将网络请求的数据,解析为json对象,然后初始化User实例
init?(data: Data) {
guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {
return nil
}
guard let name = obj["name"] as? String else {
return nil
}
guard let message = obj["message"] as? String else {
return nil
}
self.name = name
self.message = message
}
}
/// 定义请求方式
enum HTTPMethod: String {
case GET
case POST
}
/// 定义请求类
protocol Request {
var host: String {get}
var path: String {get}
var method: HTTPMethod {get}
var parameter: [String: Any] {get}
///为了所有的Request都通用,所以将User具体的返回类型,设置为关联类型
associatedtype Response
/// 将请求数据解析的功能下沉
func parse(data: Data) -> Response?
}
/// 为了任意的请求都可以通过同样的方法发送,所以定义在request的扩展上
extension Request {
func send(handler:@escaping (Response?) -> Void) {
/// 发起网络请求
let url = URL(string: host.appending(path))!
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
/// 这是尾随必闭包的写法
let task = URLSession.shared.dataTask(with: request) {
data,res,error in
if let data = data, let res = parse(data: data) {
DispatchQueue.main.async { handler(res) }
}else {
DispatchQueue.main.async { handler(nil) }
}
}
task.resume()
}
}
/// 定义用户请求的实现类
struct UserRequest: Request {
/// 这里是为了确定协议Request中,关联的类型
typealias Response = User
let name: String
let host = "https://api.onevcat.com"
var path: String {
return "/user/\(name)"
}
let method: HTTPMethod = .GET
let parameter: [String : Any] = [:]
func parse(data: Data) -> User? {
return User(data: data)
}
}
/// 调用请求
class HomeViewController: ViewController {
override func viewDidLoad() {
let request = UserRequest(name: "onecat")
request.send { (user) in
if let user = user {
print(user.name,user.message)
}
}
}
}
复制代码
以上的结构,最大的问题是Request集成了太多的功能,不仅定义了host的值,还包括了如何解析数据,而且网络请求的方法send也是绑定在一起,这是不合理的,它应该只关心请求的入口和期望的响应类型
升级改造后
- 将发送网络请求独立与网络请求类
- 将数据解析在数据模型Response中处理
struct User: Decodable {
let name: String
let message: String
/// 将网络请求的数据,解析为json对象,然后初始化User实例
init?(data: Data) {
guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {
return nil
}
guard let name = obj["name"] as? String else {
return nil
}
guard let message = obj["message"] as? String else {
return nil
}
self.name = name
self.message = message
}
/// 定义数据解析
static func parse(data: Data) -> User? {
return User(data: data)
}
}
/// 定义请求方式
enum HTTPMethod: String {
case GET
case POST
}
/// 将发送网络请求 独立出来
protocol Client {
/**
protocol Client {
func send(_ r: Request, handler: @escaping (Request.Response?) -> Void)
}
这种写法编译器会报错,是由于Request内包含关联类型的协议,所以不能作为独立的类型来是使用
只能够将它作为类型约束,来限制输入参数request,以下才是正确的写法
*/
func send<T: Request>(_ r: T, handle: @escaping(T.Response?) -> Void)
var host: String { get }
}
/// 实现网络请求
struct URLSessionClient: Client {
let host = "https://api.onevcat.com"
func send<T>(_ r: T, handle: @escaping (T.Response?) -> Void) where T : Request {
let url = URL(string: host.appending(r.path))!
var request = URLRequest(url: url)
request.httpMethod = r.method.rawValue
let task = URLSession.shared.dataTask(with: request) {
data , _ , error in
if let data = data , let res = T.Response.parse(data: data) {
DispatchQueue.main.async { handle(res) }
}else{
DispatchQueue.main.async { handle(nil) }
}
}
task.resume()
}
}
/// 数据解析:
/// 将发送请求的部分和本事分离开了,请求不需要知道如何解析得到的数据
/// 交给Response完成
protocol Decodable {
static func parse(data: Data) -> Self?
}
/// 定义请求类
protocol Request {
var path: String { get }
var method: HTTPMethod { get }
var parameter: [String: Any] { get }
/// 为了所有的Request都通用,所以将User具体的返回类型,设置为关联类型
/// 需要在Request的Response关联类型中为它加上这个限制,可以保证所有的Response都可以对数据进行解析
associatedtype Response: Decodable
}
struct UserRequest: Request {
typealias Response = User
let name: String
var path: String {
return "/user/\(name)"
}
let method: HTTPMethod = .GET
let parameter: [String : Any] = [:]
}
/// 请求调用方式
class HomeViewController: ViewController {
override func viewDidLoad() {
URLSessionClient().send(UserRequest(name: "onevcat")) { user in
if let user = user {
print("\(user.message) from \(user.name)")
}
}
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END