[swift 進階]讀書筆記-第十章:協議 C10P1 面向協議程式設計 Overload Resolution for Free Functions

liaoWorking在掘金發表於2019-04-25

第十章:協議 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)
    } 
}
複製程式碼
  1. 對自定義的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 方法變為了協議的一個自定義入口

相關文章