Swift - 面向协议编程(POP)
一、OOP与POP
面向对象程序设计
(Object Oriented Programming)
其本质是以建立模型体现出来的抽象思维过程和面向对象的方法。模型是用来反映现实世界中事物特征的。任何一个模型都不可能反映客观事物的一切具体特征,只能对事物特征和变化规律的一种抽象,且在它所涉及的范围内更普遍、更集中、更深刻地描述客体的特征。通过建立模型而达到的抽象是人们对客体认识的深化。来自百度百科
正是因为化零为整的功效,方便储存数据然后传输! 面向对象的设计,在众多语言里面被采用!OC - Swift
主流方式也是 OOP
. 这个相比大家已经非常熟悉了,这里也不再啰嗦。今天的主角是 POP (Protocol Oriented Programming)
一 面向协议编程
面向对象的困境
王巍面向协议编程 这一篇文章里面就有详细表达!
- 横切关注点
- 动态派发安全性
- 菱形缺陷
二、POP 面向协议编程
面向协议编程的思维,在 Swift
开发中非常非常重要!可以说如果你用好了 POP
那么你的项目绝对逼格不是一个 level
下面我们通过解决 OOP
问题的思路展开分析
POP 解决横切关注点
横切关注点 (Cross-Cutting Concerns)
那就是我们很难在不同继承关系的类里共用代码! 现在我们通过面向协议的方式,任何遵循协议的,都可以默认添加 name
属性以及sayHello()
方法!
protocol LGProtocl {
/// 协议属性
var name: String {get}
/// 协议方法
func sayHello()
}
- 但是这里还有一个问题:缺乏实现!如果这样提供声明,我还需要在每一个类里面实现,有很多时候其实这些方法都是共有,不需要太多特定实现
- 幸好在
WWDC 2015
和Swift 2
发布时,Apple
为协议引入了一个新特性一 协议扩展 它为 Swift 语言带来了一次革命性的变化。
struct LGTeacher: LGProtocl{
var name: String
func sayHello() {
print("你好")
}
}
- 通过协议定义,提供实现的入口,遵循协议的类型需要对其进行实现
- 协议扩展,为入口提供默认实现。根据入口提供额外实现
这样的操作有什么作用了? 一 万物皆 lg
// 声明协议
extension LGCompatible {
/// Reactive extensions.
public static var lg: LGRxSwift.Reactive<Self>.Type
/// Reactive extensions.
public var lg: LGRxSwift.Reactive<Self>
}
// NSObject 实现
extension NSObject : LGCompatible { }
- 这样完美实现了万物皆lg的特性
- 然后通过
LGCompatible
引申到Reactive
响应式类或者结构体 - 最后通过不断拓展
Reactive
的能力,就能完美切合
POP 解决动态派发安全性
对于没有实现 LGProtocl
的类型,编译器将返回错误,因此不存在消息误发送的情况
// Compiler Error:
// 'LGTeacher' does not conform to protocol 'LGProtocl'
// protocol requires function 'sayHello()'
POP 解决菱形缺陷
最后我们看看多继承。多继承中存在的一个重要问题是菱形缺陷,也就是子类无法确定使用哪个父类的方法。在协议的对应方面,这个问题虽然依然存在,但却是可以唯一安全地确定的。
这里很遗憾POP在 解决菱形缺陷
这一点上也存在同样的BUG , 因为多个协议存在相同的协议属性、协议方法,遵循者也是无法确定的!⚠️ 我们在平时开发中一定要尽量规避同名协议遵循问题,我们在模块划分上面一定要做做到彻底,尽管 Swift 还不能很好地处理多个协议的冲突 但是我们可以在协议层功能抽层严格机智处理上浮与下沉功能。
举个例子:🌰
-
我们刚刚是不是做了
LGCompatible
, 它是我们功能的入口,万物皆lg
,进来,那么这层协议只需要提供入口就完毕 -
同时提供接口过度能力,把
LGReactiveCompatible
的lg
过度到Reactive
层
extension LGReactiveCompatible {
/// Reactive extensions.
public static var lg: Reactive<Self>.Type {
get { return Reactive<Self>.self }
set {// this enables using Reactive to "mutate" base type }
}
/// Reactive extensions.
public var lg: Reactive<Self> {
get { return Reactive(self) }
set { // this enables using Reactive to "mutate" base object }
}
}
-
Reactive
层, 根据业务划分开来,达到逻辑代码下沉效果!
public struct Reactive<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
- 根据
Reactive
里面关联的Base
类型来确定不同的响应式功能 - 比如:
extension Reactive where Base: UISwitch
其中UISwitch
可以换成UITableView
、UITextField
、UIView
... 不断业务下沉!
相信到这里你已经感受到了面向协议编程的方便之处,但是还有一个非常重要的特性没有展现出来就是 一 耦合度大大降低,代码分层,逻辑更清晰
三、POP 网络
我们在实际开发中,网络请求是一个非常重要的模块
Alamofire.request("http://127.0.0.1:5000/pythonJson/")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseData { response in
switch response.result {
case .success:
print(response)
let _ = LGLoginClient.lgJson(data: response.data)
case .failure(let error):
print(error)
}
}
- 上面这段代码,没有错!但是如果你是一个资深iOS开发,肯定会发现问题!
- 如果你直接在
ViewController
(代表应用层) 直接这么网络请求,耦合度是非常大的(应用层与网络层耦合在一起) - 还有到处嵌套,可复用性特别低
- 应用层其实根本不应该关心网络请求的
method、接口、参数
说白了我也不想关心 - 如果系统模块化处理,那我就非常 happy 😄😄😄
1️⃣:网络信息能力
enum LGHTTPMethod: String {
case GET
case POST
}
protocol LGRequest {
var host: String { get }
var path: String { get }
var method: LGHTTPMethod { get }
var parameter: [String: Any] { get }
associatedtype Response
func parse(data: Data) -> Response?
}
-
LGHTTPMethod
提供本模块LGRequest
需要的请求方法枚举 -
LGRequest
是登录注册模块的请求能力赋予者,通过面向协议的方式给我们的模块提供能力 -
Response
这个关联类型,方便后面json
转模型,设置这个泛型类型是能够通用化
2️⃣:模块信息层
struct LGLoginRequest: LGRequest {
typealias Response = LGPerson
let name: String
let host = "http://127.0.0.1:5000"
var path: String {
return "/pythonJson/getTeacherInfo/?username=\(name)"
}
let method: LGHTTPMethod = .GET
let parameter: [String: Any] = [:]
func parse(data: Data) -> LGPerson? {
return LGPerson(data: data)
}
}
-
LGLoginRequest
遵循LGRequest
获得host、path、method、parameter、parse
处理能力,在这里可以直接处理,就不需要到应用层再去传值!
3️⃣:网络请求能力
extension LGRequest {
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, response, error in
if let data = data, let res = self.parse(data: data) {
DispatchQueue.main.async { handler(res) }
} else {
DispatchQueue.main.async { handler(nil) }
}
}
task.resume()
}
}
- 因为面向协议,我们利用协议提供公用网络请求能力
- 其中
Response
就是相应模型的具体类型
4️⃣:应用层调用
override func viewDidLoad() {
super.viewDidLoad()
let request = LGPersonRequest(name: "Cooci")
request.send { (person) in
self.updataUI(person: person!)
}
}
- 应用层与网络层完全分隔开来
- 应用层只提供必要的参数信息,具体调用哪个接口,怎么处理交给下层
-
LGPersonRequest
模块信息处理提供层 -
LGPersonRequest
同时还具备调用网络能力
5️⃣:POP网络优化重构
很显然我们的 LGRequest
这个家伙的能力太强了!能提供信息,还能发起请求,连序列化的处理能力也是由LGRequest
提供!优化重构迫在眉睫。。。。
1:信息提供能力者
protocol LGRequest {
var path: String { get }
var method: LGHTTPMethod { get }
var parameter: [String: Any] { get }
associatedtype Response: LGDecodable
}
struct LGPersonRequest: LGRequest {
typealias Response = LGPerson
let name: String
var path: String {
return "/pythonJson/getTeacherInfo/?username=\(name)"
}
let method: LGHTTPMethod = .GET
let parameter: [String: Any] = [:]
}
- 把公共提供的
host
提取出去 - 不再提供
LGRequest
网络请求能力 - 序列化交付给具体的模型,提供一个序列化能力
LGDecodable
protocol ClientProtocol {
var host: String { get }
func send<T: LGRequest>(_ r: T, handler: @escaping (T.Response?) -> Void)
}
class LGClient: ClientProtocol{
static let manager = LGClient()
let host: String = "http://127.0.0.1:5000"
func send<T>(_ r: T, handler: @escaping (T.Response?) -> Void) where T : LGRequest {
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, response, error in
if let data = data, let res = T.Response.parse(data: data) {
DispatchQueue.main.async { handler(res) }
} else {
DispatchQueue.main.async { handler(nil) }
}
}
task.resume()
}
}
- 提供一个网络管理类
LGClient
- 管理网络请求能力和公共提供参数
2:网络能力提供者
class LGLoginClient: LGClient {
override func send<T>(_ r: T, handler: @escaping (T.Response?) -> Void) where T : LGRequest {
switch r.path {
case let string where string.contains("/pythonJson/getTeacherInfo"):
print("123456")
handler(nil)
default:
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, response, error in
if let data = data, let res = T.Response.parse(data: data) {
DispatchQueue.main.async { handler(res) }
} else {
DispatchQueue.main.async { handler(nil) }
}
}
task.resume()
}
}
}
- 模块网路能力层
LGLoginClient
重写,根据不同的接口分块处理 - 其中序列化层交给泛型模型 (
T.Response
) 处理 - 当然大家这里还可以将网络能力继续下沉,就给具体网络综合请求者去处理
3:序列化能力提供层
extension LGPerson: LGDecodable {
static func parse(data: Data) -> LGPerson? {
return LGPerson(data: data)
}
}
- 这里序列化就简单表达了,大家可以调用一些优化的三方框架
6️⃣:小结
- POP网络,让应用层与网络层完全脱离!
- 面向协议的编程方式来提供能力,大大拓展了复用性,同时耦合度也得以处理!
- 同时这个模型(应用层 -> 信息提供层 -> 网络层) 编程思维也是比较容易理解,操作更容易上手!
- 还有这样的编程习惯也符合开发流程(一般都是由后台开发人员做出相应接口,我们才会去做网络调试)
- 后期维护简单,后期更改,我们只需要对信息提供层处理响应配置,根本不需要去动应用层或者网络层
- 当然这也是主流开发思维,作为一名中高级iOS开发人员不动
POP网络编程
那么我估计你需要好好学习咯!💪💪💪
由于篇幅问题,这一篇面向协议编程,我们先介绍到这里!下一篇介绍 当Moya遇上RxSwift,当北京遇上西雅图,当
Moya
遇上RxSwift
,简直不要太爽!就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!