什么是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
}