什么是Protocol?
Protocol是Swift中的一种自定义类型,可以使用protocol定义某种约定,而不是某一种类型,一般用于表示某种类型的共性。
Protocol 用法
定义一个protocol
protocol PersonProtocol {
func getName()
func getSex()
}
某个class、struct或者enum要遵守这种约定的话,需要实现约定的方法
struct Person: PersonProtocol {
func getName() {
print("MelodyZhy")
}
func getSex() {
print("boy")
}
}
protocol中的约定方法,当方法中有参数时是不能有默认值的
protocol中也可以定义属性,但必须明确指定该属性支持的操作:只读(get)或者是可读写(get set)
protocol PersonProtocol {
// 我们也可以在protocol中定义属性
// ⚠️必须明确指定该属性支持的操作:只读(get)或者是可读写(get set)
var height: Int { get set }
func getName()
func getSex()
// protocol中的约定方法,当方法中有参数时是不能有默认值的
// ❌ Default argument not permitted in a protocol method
// func getAge(age: Int = 18)
func getAge(age: Int)
}
虽然height在protocol中是一个computed property,但在遵守该约定的类型中可以简单的定义成一个stored property
当protocol中定义了一个只读属性,其实我们也可以在遵守该约定的类型中完成该属性的可读可写
protocol PersonProtocol {
var height: Int { get set }
var weight: Int { get }
func getName()
func getSex()
func getAge(age: Int)
}
struct Person: PersonProtocol {
var height = 178
var weight = 120
func getName() {
print("MelodyZhy")
}
func getSex() {
print("boy")
}
func getAge(age: Int) {
print("age = \(age)")
}
}
var person = Person()
person.height // 178
person.height = 180
person.height // 180
person.weight // 120
// 可以更改(但在以前版本的Swift中是不能直接这样更改的
// 需要借助一个内部的stored property
// 然后把这个属性设计成一个computed property实现 get 和 set 方法)
person.weight = 130 // 130
// 当我们把person从Person转换成PersonProtocol时 他就是只读的了
// ❌Cannot assign to property: 'weight' is a get-only property
(person as PersonProtocol).weight = 120
如何定义可选的protocol属性或者方法?
@objc protocol PersonProtocol {
optional var height: Int { get set }
optional var weight: Int { get }
optional func getName()
optional func getSex()
optional func getAge(age: Int)
}
class Person: PersonProtocol {
// 如果想提供可选的约定方法或者属性那么只能定义@objc的protocol
// 并且这种约定只能class能遵守
}
protocol可以继承,当然struct、class、enum都可以同时遵守多个约定
// 例如:
protocol PersonProtocol {
var height: Int { get set }
var weight: Int { get }
func getName()
func getSex()
func getAge(age: Int)
}
protocol Engineer: PersonProtocol {
var good: Bool { get }
}
protocol Animal {
}
struct Person: Engineer, Animal {
// 省略了该实现约定的方法和属性
}
这些protocol需要我们掌握
CustomStringConvertible
struct Point {
var x: Int
var y: Int
}
let p = Point(x: 10, y: 10)
print(p) // Point(x: 10, y: 10)
// 如果我们想让打印界面变成 x = 10, y = 10
// 那么我们就要遵守 CustomStringConvertible 这个 protocol
// 来看下实现
extension Point: CustomStringConvertible {
// 这个 protocol 只有一个约定定义一个名为description的属性
var description: String {
return "x = \(self.x), y = \(self.y)"
}
}
print(p) // x = 10, y = 10\n
BooleanType
// 如何更优雅的判断该点是不是原点(0, 0)
// 来一起看下
extension Point: BooleanType {
// 遵守BooleanType这个protocol
// 这个protocol也只需要一个实现一个名为boolValue的属性
var boolValue: Bool {
return self.x == 0 && self.y == 0
}
}
let p = Point(x: 10, y: 10)
// 这样我们就能这样进行判断了
if p {
print("是原点(0, 0)")
} else {
print("不是原点")
}
// 这个protocol可以用到项目的好多地方,开发自己的脑洞去吧!
Equatable
extension Point: Equatable {}
func == (lP: Point, rP: Point) -> Bool {
let equalX = lP.x == rP.x
let equalY = lP.y == rP.y
return equalX && equalY
}
let lP = Point(x: 10, y: 10)
let rP = Point(x: 10, y: 10)
// 不要以为是我们重载了 == 函数 就能自动推导出 !=
// 其实这都是 Equatable 这个 protocol 的功劳
// 当然我们如果要遵守 Equatable 这个 protocol 就必须实现 == 函 数 或者 != 函数
// 如果我们只是重载了 == 函数 是不能用 !=
if lP != rP {
print("unequal")
} else {
print("equal")
}
Comparable
// 想遵守 Comparable 这个 protocol 必须遵守 Equatable
// 同时用必须实现 < 函数 或者 > 函数
extension Point: Comparable {}
// 同样 > 函数会自动推导出来
func < (lP: Point, rP: Point) -> Bool {
// 随意定义的规则 不必在意
let x = lP.x - rP.x
let y = lP.y - rP.y
return x < y
}
// 实现了 Comparable 我们就可以把 Point 放到 Array 中,同时支持使用各种排序方法
protocol extension
protocol extension 最关键的一点就是能在 protocol extension 方法中获取 protocol 的属性,因为Swift编译器知道任何一个遵守 protocol 的自定义类型,一定会定义这个 protocol 约定的各种属性,既然这样我们就可以在 protocol extension 中添加默认的实现了。这也是为什么会有 protocol oriented programming 这个概念,但这时候肯定会有人说我通过面对对象的编程方式也可以实现,但为什么要用遵守 protocol 的方法呢,这个要等到了解 extension 中的 type constraints 后解释...
先看一个通过 protocol extension 添加默认实现的代码例子
// 定义一个人属性的 protocol
protocol PersonProperty {
var height: Int { get } // cm
var weight: Double { get } // kg
// 判断体重是否合格的函数
func isStandard() -> Bool
}
extension PersonProperty {
// 给 protocol 添加默认的实现
func isStandard() -> Bool {
return self.weight == Double((height - 100)) * 0.9
}
// 给 protocol 添加默认属性
var isPerfectHeight: Bool {
return self.height == 178
}
}
struct Person: PersonProperty {
var height: Int
var weight: Double
// 如果自定义类型里面创建了遵守的 protocol 中的方法
// 那么他将覆盖 protocol 中的方法
// func isStandard() -> Bool {
// return true
// }
}
// 创建遵守 PersonProperty 的自定义类型
let p = Person(height: 178, weight: 61.5)
// 那么 p 这个自定义类型 天生就有判断这个人身高体重是否合格的方法
p.isStandard() // false
// 同样天生具有判断是否是 Perfect Height 的属性
p.isPerfectHeight // true
protocol extension 中的 type constraints
这相当于给 protocol extension 中的默认实现添加限定条件,写法如下
// 运动因素的 protocol
protocol SportsFactors {
// 运动量
var sportQuantity: Double { get }
}
// 下面这种写法就用到了 extension 中的 type constraints
// 意思是 只有同时遵守了 SportsFactors 和 PersonProperty 时
// 才使 PersonProperty 获得扩展 并提供带有 sportQuantity 属性的 isStandard 方法
extension PersonProperty where Self: SportsFactors {
func isStandard() -> Bool {
// 随意写的算法 不要在意
return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
}
}
protocol oriented programming 的优点
1、首先继承是 class 专有的,所以它不能用来扩展其他类型,但 protocol 是没有这种局限性的
2、试想一下,上面的代码你用面对对象的编程方式的话可能你就需要多一个运动量的属性,同时也要修改 isStandard 函数,一切看起来特别自然,随着后续需求的更改可能会有更多因素影响是否是合格的体重,那么这时候你就会在不知不觉中将你代码的耦合度成倍提高,其实对于这个类来说,他完全不需要知道是否是合格体重的计算细节,所以我们完全可以把这些类型无关的细节从类型定义上移出去,用一个 protocol 封装好这些细节,然后让其成为这个类型的一种修饰,这就是POP的核心思想。
3、当有多种因素制约是否是合格体重时,我们可以用多个 protocol 来对该类型进行修饰,每一种修饰的相关细节,我们都在对应的 protocol extension 中单独的封装起来,这样就大大降低了代码的耦合度,同时代码的可维护性也得到了相应的提高。swift标准库中大部分都是用这种思想构建的。
最终我们的写法可能是这样的
struct Person: PersonProperty, SportsFactors ... {
var height: Int
var weight: Double
var sportQuantity: Double
}