内容简介:在上述面向对象的实现中我们可以看到发起一个网络请求一般会有三个步骤我们可以把这三个步骤进行抽象,用三个
class Light { func 插电() {} func 打开() {} func 增大亮度() {} func 减小亮度() {} } class LEDLight: Light {} class DeskLamp: Light {} func 打开(物体: Light) { 物体.插电() 物体.打开() } func main() { 打开(物体: DeskLamp()) 打开(物体: LEDLight()) } 复制代码
在上述面向对象的实现中 打开
方法似乎只局限于 Light
这个类和他的派生类。如果我们想描述 打开
这个操作并且不单单局限于 Light
这个类和他的派生类,(毕竟柜子、桌子等其他物体也是可以打开的)抽象 打开
这个操作,那么 protocol
就可以派上用场了。
protocol Openable { func 准备工作() func 打开() } extension Openable { func 准备工作() {} func 打开() {} } class LEDLight: Openable {} class DeskLamp: Openable {} class Desk: Openable {} func 打开<T: Openable>(物体: T) { 物体.准备工作() 物体.打开() } func main() { 打开(物体: Desk()) 打开(物体: LEDLight()) } 复制代码
普通的网络请求
// 1.准备请求体 let urlString = "https://www.baidu.com/user" guard let url = URL(string: urlString) else { return } let body = prepareBody() let headers = ["token": "thisisatesttokenvalue"] var request = URLRequest(url: url) request.httpBody = body request.allHTTPHeaderFields = headers request.httpMethod = "GET" // 2.使用URLSeesion创建网络任务 URLSession.shared.dataTask(with: request) { (data, response, error) in if let data = data { // 3.将数据反序列化 } }.resume() 复制代码
我们可以看到发起一个网络请求一般会有三个步骤
- 准备请求体(URL、parameters、body、headers...)
- 使用框架创建网络任务(URLSession、Alamofire、AFN...)
- 将数据反序列化(Codable、Protobuf、SwiftyJSON、YYModel...)
我们可以把这三个步骤进行抽象,用三个 protocol
进行规范.
规范好之后,再由各个类型实现这三个协议,就可以随意组合使用.
抽象网络请求步骤
Parsable
首先我们定义 Parsable
协议来抽象反序列化这个过程
protocol Parsable { static func parse(data: Data) -> Result<Self> } 复制代码
Parsable
协议定义了一个静态方法,这个方法可以从Data -> Self
例如 User
遵循 Parsable
协议,就要实现从Data转换到User的 parse(:)
方法
struct User { var name: String } extension User: Parsable { static func parse(data: Data) -> Result<User> { // ...实现Data转User } } 复制代码
Codable
我们可以利用 swift协议扩展
的特性给遵循 Codable
的类型添加一个默认的实现
extension Parsable where Self: Decodable { static func parse(data: Data) -> Result<Self> { do { let model = try decoder.decode(self, from: data) return .success(model) } catch let error { return .failure(error) } } } 复制代码
这样 User
如果遵循了 Codable
,就无需实现 parse(:)
方法了
于是反序列化的过程就变这样简单的一句话
extension User: Codable, Parsable {} URLSession.shared.dataTask(with: request) { (data, response, error) in if let data = data { // 3.将数据反序列化 let user = User.parse(data: data) } 复制代码
到这里可以想一个问题,如果data是个模型数组该怎么办?是不是在 Parsable
协议里再添加一个方法返回一个模型数组?然后再实现一遍?
public protocol Parsable { static func parse(data: Data) -> Result<Self> // 返回一个数组 static func parse(data: Data) -> Result<[Self]> } 复制代码
这样也不是不行,但是还有更swift的方法,这种方法swift称之为条件遵循
// 当Array里的元素遵循Parsable以及Decodable时,Array也遵循Parsable协议 extension Array: Parsable where Array.Element: (Parsable & Decodable) {} 复制代码
URLSession.shared.dataTask(with: request) { (data, response, error) in if let data = data { // 3.将数据反序列化 let users = [User].parse(data: data) } 复制代码
从这里可以看到swift协议是非常强大的,使用好了可以少些很多代码,在swift标准库中有很多这样的例子。
protobuf
当然,如果你使用 SwiftProtobuf
,也可以提供它的默认实现
extension Parsable where Self: SwiftProtobuf.Message { static func parse(data: Data) -> Result<Self> { do { let model = try self.init(serializedData: data) return .success(model) } catch let error { return .failure(error) } } } 复制代码
反序列化的过程也和刚才的例子一样,调用 parse(:)
方法即可
Request
现在我们定义 Request
协议来抽象准备请求体这个过程
protocol Request { var url: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get } var headers: HTTPHeaders? { get } var httpBody: Data? { get } /// 请求返回类型(需遵循Parsable协议) associatedtype Response: Parsable } 复制代码
我们定义了一个关联类型:遵循 Parsable
的 Response
是为了让实现这个协议的类型指定这个请求返回的类型,限定 Response
必须遵循 Parsable
是因为,我们会用到 parse(:)
方法来进行反序列化。
我们来实现一个通用的请求体
struct NormalRequest<T: Parsable>: Request { var url: String var method: HTTPMethod var parameters: [String: Any]? var headers: HTTPHeaders? var httpBody: Data? typealias Response = T init(_ responseType: Response.Type, urlString: String, method: HTTPMethod = .get, parameters: [String: Any]? = nil, headers: HTTPHeaders? = nil, httpBody: Data? = nil) { self.url = urlString self.method = method self.parameters = parameters self.headers = headers self.httpBody = httpBody } } 复制代码
是这样使用的
let request = NormalRequest(User.self, urlString: "https://www.baidu.com/user") 复制代码
如果服务端有一组接口 https://www.baidu.com/user
https://www.baidu.com/manager
https://www.baidu.com/driver
我们可以定义一个 BaiduRequest
,把URL或者公共的headers和body拿到 BaiduRequest
管理
// BaiduRequest.swift private let host = "https://www.baidu.com" enum BaiduPath: String { case user = "/user" case manager = "/manager" case driver = "/driver" } struct BaiduRequest<T: Parsable>: Request { var url: String var method: HTTPMethod var parameters: [String: Any]? var headers: HTTPHeaders? var httpBody: Data? typealias Response = T init(_ responseType: Response.Type, path: BaiduPath, method: HTTPMethod = .get, parameters: [String: Any]? = nil, headers: HTTPHeaders? = nil, httpBody: Data? = nil) { self.url = host + path.rawValue self.method = method self.parameters = parameters self.httpBody = httpBody self.headers = headers } } 复制代码
创建也很简单
let userRequest = BaiduRequest(User.self, path: .user) let managerRequest = BaiduRequest(Manager.self, path: .manager, method: .post) 复制代码
Client
最后我们定义 Client
协议,抽象发起网络请求的过程
enum Result<T> { case success(T) case failure(Error) } typealias Handler<T> = (Result<T>) -> () protocol Client { // 接受一个遵循Parsable的T,最后回调闭包的参数是T里边的Response 也就是Request协议定义的Response func send<T: Request>(request: T, completionHandler: @escaping Handler<T.Response>) } 复制代码
URLSession
我们来实现一个使用 URLSession
的 Client
struct URLSessionClient: Client { static let shared = URLSessionClient() private init() {} func send<T: Request>(request: T, completionHandler: @escaping (Result<T.Response>) -> ()) { var urlString = request.url if let param = request.parameters { var i = 0 param.forEach { urlString += i == 0 ? "?\($0.key)=\($0.value)" : "&\($0.key)=\($0.value)" i += 1 } } guard let url = URL(string: urlString) else { return } var req = URLRequest(url: url) req.httpMethod = request.method.rawValue req.httpBody = request.httpBody req.allHTTPHeaderFields = request.headers URLSession.shared.dataTask(with: req) { (data, respose, error) in if let data = data { // 使用parse方法反序列化 let result = T.Response.parse(data: data) switch result { case .success(let model): completionHandler(.success(model)) case .failure(let error): completionHandler(.failure(error)) } } else { completionHandler(.failure(error!)) } } } } 复制代码
三个协议实现好之后 例子开头的网络请求就可以这样写了
let request = NormalRequest(User.self, urlString: "https://www.baidu.com/user") URLSessionClient.shared.send(request) { (result) in switch result { case .success(let user): // 此时拿到的已经是User实例了 print("user: \(user)") case .failure(let error): printLog("get user failure: \(error)") } } 复制代码
Alamofire
当然也可以用 Alamofire
实现 Client
struct NetworkClient: Client { static let shared = NetworkClient() func send<T: Request>(request: T, completionHandler: @escaping Handler<T.Response>) { let method = Alamofire.HTTPMethod(rawValue: request.method.rawValue) ?? .get var dataRequest: Alamofire.DataRequest if let body = request.httpBody { var urlString = request.url if let param = request.parameters { var i = 0 param.forEach { urlString += i == 0 ? "?\($0.key)=\($0.value)" : "&\($0.key)=\($0.value)" i += 1 } } guard let url = URL(string: urlString) else { print("URL格式错误") return } var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method.rawValue urlRequest.httpBody = body urlRequest.allHTTPHeaderFields = request.headers dataRequest = Alamofire.request(urlRequest) } else { dataRequest = Alamofire.request(request.url, method: method, parameters: request.parameters, headers: request.headers) } dataRequest.responseData { (response) in switch response.result { case .success(let data): // 使用parse(:)方法反序列化 let parseResult = T.Response.parse(data: data) switch parseResult { case .success(let model): completionHandler(.success(model)) case .failure(let error): completionHandler(.failure(error)) } case .failure(let error): completionHandler(.failure(error)) } } } private init() {} } 复制代码
我们试着发起一组网络请求
let userRequest = BaiduRequest(User.self, path: .user) let managerRequest = BaiduRequest(Manager.self, path: .manager, method: .post) NetworkClient.shared.send(managerRequest) { result in switch result { case .success(let manager): // 此时拿到的已经是Manager实例了 print("manager: \(manager)") case .failure(let error): printLog("get manager failure: \(error)") } } 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 面向Python,面向对象(基础)
- 面向Python,面向对象(基础3)
- Swift 中的面向协议编程:是否优于面向对象编程?
- 详解nginx的请求限制(连接限制和请求限制)
- angular请求防抖,以及处理第一次请求失效
- RxHttp 一条链发送请求,新一代Http请求神器(一)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
爆款:如何打造超级IP
【美】安妮塔•埃尔伯斯 / 杨雨 / 中信出版社 / 2016-1-10 / 49
哈佛商学院IP运营与产品管理方法论第一书,翻转长尾理论的重要著作! 电影大片、当红炸子鸡、百万畅销书背后的运营逻辑是什么? 《五十度灰》、Lady Gaga、维多利亚的秘密有何共同秘密? 漫威如何将蜘蛛侠、X战警、绿巨人打造成金矿? 皇马如何打造体育IP,一跃成为全球收 入最高的足球俱乐部? 爆款策略如何运用于电影、电视、音乐、出版、体育与商业各领域? ----......一起来看看 《爆款:如何打造超级IP》 这本书的介绍吧!