第十章:協議 Protocol Protocol-Oriented Programming
前言:
swift 中的協議還是很酷的
?
1.可以當做代理
來使用。
2.可以讓結構體
,列舉
來滿足協議。
3.還可以通過協議的extension
為協議新增新方法。
4.協議允許我們動態派發
。
5.OC中共享程式碼通常使用的繼承,swift中可以通過使用的是協議來共享程式碼
。
6.你可以為你的類新增協議去達到功能點整合
。
10.1 面向協議程式設計 Overload Resolution for Free Functions
這一小節舉了個例子來介紹面相協議程式設計的幾個使用場景
。
圖形渲染的Demo
將 Core Graphics 的 CGContext 渲染到螢幕上,或者建立一個 SVG 格式的圖形檔案。我們可以從定義繪圖 API 的最 小功能集的協議開始進行實現
1.先寫協議方法
protocol Drawing {
mutating func addEllipse(rect: CGRect, fill: UIColor) mutating
func addRectangle(rect: CGRect, fill: UIColor)
}
複製程式碼
2.為CGContext新增擴充套件來滿足協議
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)
}
}
複製程式碼
-
對自定義的SVG類新增擴充套件來滿足協議
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)) } } 複製程式碼
4.正式使用
var context: Drawing = SVG()
let rect1 = CGRect(x: 0, y: 0, width: 100, height: 100)
let rect2 = CGRect(x: 0, y: 0, width: 50, height: 50)
context.addRectangle(rect: rect1, fill: .yellow)
context.addEllipse(rect: rect2, fill: .blue)
複製程式碼
協議擴充套件
經常看swift標準庫API的同學肯定都用見過 蘋果官方對協議擴充套件的使用。 這裡講講優點: 1.不需要被強制使用某個父類
2.可以讓已經存在的型別滿足協議(比如我們讓CGContext滿足了Drawing)。子類就沒那麼靈活了,如果 CGContext 是一個類的話,我們無法以追溯的方式去變更它的父類。
3.協議既可以用於類,也可以用於結構體,而父類就無法和結構體一起使用了
4.當處理協議時,我們無需擔心方法重寫或者在正確的時間呼叫super這樣的問題
在協議擴充套件中重寫方法
沿著上面的Demo我們再看一個使用場景。
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))
}
}
複製程式碼
我們去呼叫:
var sample = SVG()
sample.addCircle(center: .zero, radius: 20, fill: .red) print(sample)
/*
<svg>
<circle cy="0.0" fill="#010000" r="20.0" cx="0.0"/> </svg>
*/
複製程式碼
發現正如我們所預料的,
如果我們把sample強轉為Drawing
var otherSample: Drawing = SVG() otherSample.addCircle(center: .zero, radius: 20, fill: .red)
print(otherSample)
/*
<svg>
<ellipse cy="-20.0" fill="#010000" ry="40.0" rx="40.0" cx="-20.0"/> </svg>
*/
複製程式碼
它返回的是 ellipse 元素,而不是我們所期望的 circle。
當我們將 otherSample 定義為 Drawing 型別的變數時,編譯器會自動將 SVG 值封裝到一個代表協議的型別中,這個封裝被稱作存在容器
(existential container)
當我們對存在容器呼叫 addCircle 時,方法是靜態派發
的
想要將 addCircle 變為動態派發,我們可以將它新增到協議定義裡
:
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 方法變為了協議的一個自定義入口