Swift的面向协议开发啥意思?有啥好处呢?
官方解释
面向协议编程,全称Protocol Oriented Programming,简称POP, 是 Apple 在 WWDC2015 上提出的一种编程范式,其已成为 Swift 的基础库。
在讲面向协议之前,介绍下协议的概念。
关于协议的概念,在苹果的官网是如下定义的:“协议定义了适合特定任务或功能的方法、属性和其他需求的蓝图。然后,类、结构体或枚举可以遵循该协议来提供这些需求的实际实现。任何满足协议要求的类型都被称为遵循该协议。”见 Swift 编程语言(Swift 4.0.3)部分。
这个比较难理解。我就结合个人经验,从几个方面理解下:
协议即Protocol,类似Java语言中Interface(接口),用于模块间通讯。但是又不完全类似接口。
协议在Object-C中使用的比较多,一般是结合delegate(委托),实现一个VC对另一个VC传递数据或响应事件。在此场景中的协议很像接口,定义好的一个规范。
和继承的关系,在Object-C和Swift语言中,我们知道是不支持多重继承的,然而可以通过协议来实现多重继承。
协议与多态,本来与多态扯不上关系,但是Swift4.0.3版本之后的协议扩展(Protocol extension)又可以实现多态功能。
大白话解释
常规的开发模式是面向对象开发,即:万物皆对象~ 任何一个Class的对象,都可以用很多属性。
假如我们现在有两个类:Dog
和Car
,这两个类中各有一个方法run()
考虑到封装和继承,我们可以为这两个类抽取一个父类,然后将run()
方法放于父类中.
面向对象是一种不错的抽象方式,但是肯定不是最好的方式。因为狗
和汽车
根本不是同一类事物,它无法描述两个不同事物(狗和汽车)
具有某个相同特性(跑)
这一点的要求。其实利用一些特性的组合要比继承更让人接受一些。
定义一个协议
//如果只希望协议被类遵守可以在协议后面加上 :class
protocol Runable : class{
/*
*协议中既可以定义属性也可以定义方法
*注意:
1.协议中的属性和方法都不能有默认实现
2.在定义属性时,必须明确的指出该属性是一个可读可写/只读/只写
3.默认情况下protocol中的属性,必须被遵守协议的类/结构体实现
*/
var speed : Int {get}
func run()
}
如果我们希望像在OC里面一样,声明一个可选属性的协议可以这样来声明
/*
*条件:
1.必须在protocol的前面加上@objc
2.在方法或者是属性的前面加上@objc + optional
*/
@objc protocol Runable : class{
@objc optional var speed : Int {get}
@objc optional func run()
}
假如类本身没有提供协议的实现,那么通过协议扩展提供的默认实现会被调用。
protocol Runable : class{}
//默认实现条件:必须在协议的extension中实现
extension Runable
{
var speed : Int
{
return 20
}
func func()
{
print("正在奔跑中🏃🏻♀️...")
}
}
使用场景
此时,产品经理需要我们实现一个点赞抖动的效果。如果按照面向对象的方式,我们有三种实现方式:
1.创建一个父类,在父类中实现抖动相关的代码。 2.创建一个UIView拓展,在拓展中实现相关代码。 3.使用面向协议,在协议中实现相关代码。 从实现方式来看,第一种和第二种也是可以实现功能需求的开发。
分析方法一
方法一会存在一个代码耦合性的问题,如果在父类按钮,后续又添加了其他方法。 比如旋转又或者其他功能,那么就导致代码冗余在了父类当中,也会对其他调用的人,造成理解的歧义。 而且如果这新功能,需要引用其他的类来实现一些功能,那么在BaseView中的代码就非常冗余,大量的代码堆积。
分析方法二
方法二相对父类添加,确实降低了代码的耦合性问题。但是由于是拓展方法,就导致了在任何一个UIView,都通过 .xxxx
的方式调用拓展方法,即使他们根本不需要这个功能。如果被其他开发误用,那么也增加了其他开发者的困惑。
分析方法三
利用 Swift的面向协议编程,我们就可以轻松的解决这个问题。
定义好该协议并且默认实现了颤抖的方法以后,如果控件想实现颤抖的功能,比如当点赞按钮。
此时我们只需要让该按钮遵守这个协议即可,其他什么都不需要操作了,这样就可以调用 lkk_shakeable()
方法即可。
import UIKit
protocol LKKShakeable{
}
extension LKKShakeable where Self : UIView
{
func lkk_shakeable()
{
translatesAutoresizingMaskIntoConstraints = true
let posLbl = layer.position
let animation = CABasicAnimation(keyPath: "position")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.fromValue = NSValue(cgPoint:CGPoint(x: posLbl.x - 10, y: posLbl.y) )
animation.fromValue = NSValue(cgPoint:CGPoint(x: posLbl.x + 10, y: posLbl.y) )
animation.autoreverses = true
animation.duration = 0.04
animation.repeatCount = 6
layer.add(animation, forKey: nil)
translatesAutoresizingMaskIntoConstraints = false
}
}
Example:
class loginButton :UIButton , LKKShakeable{}
处理Xib文件加载
加载Xib文件的这坨代码就会经常重复Copy,那么我们可以利用面向协议的思想抽取出来,使用的地方只需要遵守该协议即可,使用起来更加灵活.
import UIKit
protocol NibEnable {
}
extension NibLoadable where Self : UIView {
static func loadFromNib(_ nibname : String? = nil) -> Self {
let loadName = nibname == nil ? "(self)" : nibname!
return Bundle.main.loadNibNamed(loadName, owner: nil, options: nil)?.first as! Self
}
}