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()
复制代码
咱们能够看到发起一个网络请求通常会有三个步骤swift
咱们能够把这三个步骤进行抽象,用三个protocol
进行规范. 规范好以后,再由各个类型实现这三个协议,就能够随意组合使用.api
首先咱们定义Parsable
协议来抽象反序列化这个过程数组
protocol Parsable {
// ps: Result类型下边会声明,这里姑且能够认为函数返回了`Self`
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
}
}
复制代码
咱们能够利用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称之为条件遵循post
// 当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标准库中有不少这样的例子。学习
固然,若是你使用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
协议来抽象准备请求体这个过程
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
协议,抽象发起网络请求的过程
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
的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
实现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)")
}
}
复制代码
咱们用三个protocol
抽象了网络请求的过程,让网络请求变得很灵活,你能够随意组合各类实现,不一样的请求体配不一样的序列化方式或者不一样的网络框架。可使用URLSession + Codable,也可使用Alamofire + Protobuf等等,极大的方便了咱们平常开发。
喵神的这篇文章是我学习面向协议的开始,给了我极大的启发:面向协议编程与 Cocoa 的邂逅