Swift的协议

我们知道Swift拥有函数、泛型、协议的特性,它们之间的协同工作能让代码的动态特性变得异常强大。
Swift 协议可以被用作代理,也可以让你对接口进行抽象 (比如 IteratorProtocol 和 Sequence)。
Swift 协议与 OC 的协议最大的不同:
(1)可以让结构体和枚举类型满足协议
(2)可以有关联类型
(3)通过协议扩展的方式为协议添加方法实现
协议允许我们进行动态派发,也就是说,在运行时程序会根据消息接收者的类型去选择正确的
方法实现。

Swift协议的特性:

可以以追溯的方式来修改任意类型,让它们满足协议。
可以使用完整的方法实现来扩展一个协议。
可以当做类型约束,也可以当做独立类型(关联类型的协议、Self约束的协议)。

代码共享

可以通过协议和协议扩展的方式实现代码共享。
协议扩展是一种可以在不共享基类的前提下共享代码的方法。协议定义了一组最小可行的方法
集合,以供类型进行实现。而类型通过扩展的方式在这些最小方法上实现更多更复杂的特性。

通过协议进行代码共享 与 通过继承的共享 相比有这几个优势:
1、我们不需要被强制使用某个父类。
2、我们可以让已经存在的类型满足协议。子类就没那么灵活了,我们无法以追溯的方式去变更 CGContext 的父类。
3、协议既可以用于类,也可以用于结构体,而父类就无法和结构体一起使用了。
4、最后,当处理协议时,我们无需担心方法重写或者在正确的时间调用super这样的问题。

示例代码理解

1、定义协议Drawing(抽象出来的方法集)

1
2
3
4
protocol Drawing {
mutating func addEllipse(rect: CGRect, fill: UIColor)
mutating func addRectangle(rect: CGRect, fill: UIColor)
}

mutating关键字:使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。所以要多考虑使用mutating来修饰方法。

2、CGContext、SVG 遵守协议Drawing,实现协议的方法

1
2
3
4
5
6
7
8
9
10
extension CGContext: Drawing {
func addEllipse(rect: CGRect, fill : UIColor) {
setFillColor( fill .cgColor)
fillEllipse (in: rect)
}
func addRectangle(rect: CGRect, fill fillColor : UIColor) {
setFillColor(fillColor .cgColor)
fill (rect)
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct SVG {
var rootNode = XMLNode(tag: "svg")
mutating func append(node: XMLNode) {
rootNode.children.append(node)
}
}
extension SVG: Drawing {
mutating func addEllipse(rect: CGRect, fill: UIColor) {
var attributes: [String:String] = rect.svgAttributes
attributes["fill "] = String(hexColor:fill )
append(node: XMLNode(tag: "ellipse", attributes: attributes))
}
mutating func addRectangle(rect: CGRect,fill: UIColor) {
var attributes: [String:String] = rect.svgAttributes
attributes["fill"] = String(hexColor:fill )
append(node: XMLNode(tag: "rect", attributes: attributes))
}
}

3、写协议扩展,协议扩展里可以定义完整的方法实现。如果协议扩展里的方法没有声明到定义的协议中就是静态派发,如果也声明到协议中就是动态派发
(也就是协议要求的方法是动态派发的,而仅定义在扩展中 的方法是静态派发的)

1
2
3
4
5
6
7
8
extension Drawing {
mutating func addCircle(center: CGPoint, radius: CGFloat,fill: UIColor) {
let diameter = radius/2
let origin = CGPoint(x: center.x - diameter, y: center.y - diameter) let size = CGSize(width: radius, height: radius)
let rect = CGRect(origin: origin, size: size)
addEllipse(rect: rect, fill : fill )
}
}

4、集成了协议的类、结构体、枚举等,他们都能拥有协议扩展里的方法。
CGContext、SVG遵守了协议Drawing的话,那么他们也将拥有了addCircle方法。

5、遵守了协议的类、结构体、对象等,他们可以在自己内部重写协议或者协议扩展中的方法,使得功能更具体更接近自己的业务逻辑需求。(如果这时候创建的)

1
2
3
4
5
6
7
8
9
10
11
extension SVG {
mutating func addCircle(center: CGPoint, radius: CGFloat,fill: UIColor) {
var attributes: [String:String] = [
"cx": "\(center.x)",
"cy": "\(center.y)",
"r": "\(radius)",
]
attributes["fill "] = String(hexColor:fill )
append(node: XMLNode(tag: "circle", attributes: attributes))
}
}

6、如果 SVG 不遵守 Drawing 协议的情况下调用addCircle方法,则是调用了的是SVG内重写的addCircle方法

1
2
3
var sample = SVG()
sample.addCircle(center: .zero, radius: 20, fill: .red)
print (sample)

如果 SVG 遵守了 Drawing 协议的情况下调用addCircle方法,则是调用了的是的协议扩展中的addCircle方法

1
2
3
var sample : Drawing = SVG() //编译器会自动将 SVG 值封装到一个代表协议的类型中,这个封装被称作存在容器
sample.addCircle(center: .zero, radius: 20, fill: .red) //静态派发,它总是会使用 Drawing 的扩展。如果它是动态派发,那么它肯定需要将方法的接收者 SVG 类型考虑在内
print (sample)

7、将 addCircle 变为动态派发,即把它添加到协议定义里

1
2
3
4
5
protocol Drawing {
mutating func addEllipse(rect: CGRect,fill: UIColor)
mutating func addRectangle(rect: CGRect,fill: UIColor)
mutating func addCircle(center: CGPoint, radius: CGFloat,fill: UIColor)
}

我们依旧可以像之前那样提供一个默认的实现。而且和之前一样,具体的类型还是可以自由地重写addCircle。因为现在它是协议定义的一部份了,它将被动态派发。在运行时,根据方法接收者的动态类型的不同,存在容器将会在自定义实现存在时对其进行调用。如果自定义实现不存在,那么它将使用协议扩展中的默认实现。addCircle 方法变为了协议的一个自定义入口。
Swift 标准库大量使用了这样的技术。像是Sequence这样的协议有非常多的必要方法,不过几乎所有的方法都有默认实现。因为方法是动态派发的,所以实现Sequence 协议的类型可以自定义默认实现,不过这并不是必须的。

在 Swift 中,协议是非常重要的构建单元。使用协议,我们可以写出灵活的代码,而不必拘泥于接口和实现的耦合。
不要过度使用协议。有时候,一个独立的像是结构体或者类的类型要比定义一个协议容易理解得多,简单的类型系统也有利于增加代码的可读性。

使用协议最大的好处在于它们提供了一种最小的实现接口。协议只对某个接口究竟应该做什么
进行定义,而把使用多种类型来具体实现这个接口的工作留给我们。同样,这也让测试变得更
加容易。我们只需要创建一个满足协议的简单的测试类型就可以开始测试工作了,而不必引入
和建立一串复杂的依赖关系。

面向协议编程:

1、根据需求功能,抽象出方法,定义协议
2、为协议添加对应扩展
3、遵守协议,然后满足协议要求的,都能使用协议和协议扩展中的功能方法。
声明在协议里的方法是动态派发,而仅定义在扩展中的方法是静态派发的。

打赏支持一下呗!