iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 30
0
Software Development

Swift 菜鳥的30天系列 第 30

Day-30 Swift 語法(26) - 最後の協定

協定類型的集合

協議可以用作儲存在集合比如數組或者字典中的類型,如同在協議作為類型。我們簡單的創建一個 AfuncProtocol 協定跟 Printer 的 class,並創建 Printer 的實例:

protocol AfuncProtocol {
    func msg()
}

class Printer:AfuncProtocol {
    func msg() {
        print("鐵人30天完成")
    }
}

let printer1 = Printer()
let printer2 = Printer()
let printer3 = Printer()

let printers:[AfuncProtocol] = [printer1,printer2,printer3]

for printer in printers {
    printer.msg()
}

我們宣告一個類型為 [AfuncProtocol] 的數組,並用我們的三個實例作為他的成員,由於它是 AfuncProtocol ,並且 AfuncProtocol 已知的訊息就是包含一個 msg() 的方法,所以 for-in 循環來訪問 printer.msg 是可以的。結果如下:

https://ithelp.ithome.com.tw/upload/images/20180118/20107701DKznogog3D.png


協定的繼承

協議可以繼承一個或者多個其他協定並且可以在它繼承的基礎之上添加更多要求。協定繼承的語法與 class 繼承的語法相似,使用逗號分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 協定內容
}

Class 專用的協定

通過 AnyObject 關鍵字到協定的繼承列表,你就可以限制協議只能被 class 類型採納( 並且不是 struct 或者enum )。

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class 專用的協定內容
}

這時 SomeClassOnlyProtocol 只能被 class 類型採納。如果在 struct 或者 enum 中嘗試採納 SomeClassOnlyProtocol 就會編譯錯誤。


協定組合

要求一個類型一次遵循多個協議是很有用的。你可以使用協議組合來複合多個協議到一個要求裡。協議組合行為就和你定義的臨時局部協議一樣擁有構成中所有協議的需求。協議組合不定義任何新的協議類型。

你可以列舉任意數量的協議,使用和符號連接( & ,使用逗號分隔。除了協議列表,協議組合也能包含 class 類型,這允許你標明一個需要的父類。

我們下面定義了兩個協定,各自有一個 String 的要求,之後我們新增一個 Person 的 struct 遵循這兩個協定。之後我們新增一個 celebrate(to:) 方法,其中 celebrate 他的參數類型為 Groups & Names ,意味著“任何同時遵循 Named 和 Aged 的協議。”它不關心具體是什麼樣的類型傳入函數,只要遵循這兩個要求的協議即可。

protocol Groups {
    var groups:String {get}
}

protocol Names {
    var names:String {get}
}

struct Person:Groups, Names {
    var groups: String
    var names: String
}

func celebrate(to celebrate: Groups & Names) {
    print("恭喜「 \(celebrate.groups) 」的成員 \(celebrate.names),完成「 30天鐵人競賽」 !")
}

let ironMan = Person(groups: "好想工作室", names: "Jeremy")
celebrate(to: ironMan)

之後我們宣告一個 Person 為一個常數實例,並賦他 groups 與 names 的值,透過 celebrate(to:) 方法,將這個實例傳入其中,因為我們 ironMan 也同時遵循 Groups & Names ,所以可以合法的調用。結果如下:

https://ithelp.ithome.com.tw/upload/images/20180118/20107701DudRfiTbFs.png


協定遵循的檢查

你可以使用類型轉換中提到的的 is 和 as 運算符來檢查協定遵循,還能轉換為指定的協定。檢查和轉換協定的語法與檢查和轉換類型是完全一樣的,

  • 如果實例遵循協議,那麼 is 運算符就會返回 true,反之則返回 false
  • as ? 向下轉換運算符返回協議的可選項,如果遵循協議就會返回協定類型的可選值,反之則返回nil。
  • as ! 向下轉換運算符強制轉換協議類型並且在失敗是觸發運行錯誤。

首先我們定義一個面積的協定,裡面有一個 Int 類型的可讀屬性 area :

protocol Area {
    var area:Int {get}
}

接下來我們定義兩個 Class (Square & Triangle) 遵循上面 Area 的協議:

class Square:Area {
    var area: Int
    init(width:Int) {
        self.area = width * width
    }
}
class Triangle:Area {
    var area: Int
    init(width:Int,height:Int) {
        self.area = width * height / 2
    }
}

之後新增一個 Cube 的 class ,但他不遵循我們 Area 的 class,因為他是用來計算體積的:

class Cube {
    var volume:Int
    init(width:Int,length:Int,height:Int) {
        volume = width * length * height
    }
}

雖然他們三個 class 不來自於相同的基類。不過因為它們都是 class ,所以它們三個類型的實例都可以用於初始化儲存類型在 AnyObject 的數組:

你可以先將三個 class 各自創建一個實例,再放到 AnyObject 的數組中:

let square = Square(width: 10)
let triangle = Triangle(width: 10, height: 10)
let cube = Cube(width: 20, length:10 , height: 10)

let areas:[AnyObject] = [square,triangle,cube]

或是你直接使用 class 來創建 AnyObject 數組中的成員,:

let areas:[AnyObject] = [Square(width:10),Triangle(width:10,height:10),Cube(width:10,length:10,height:5)]

之後我們經由 for in 語法來循環數組內的成員,並由 if let 語法,如果 area 是遵循 Area 協定的話,那麼就印出我們 haveArea 的內容,若他不遵循 Area 的協定,則會返回 nil ,並印出訊息 :

for area in areas{
    if let haveArea = area as? Area {
        print("\(haveArea.name)面積為:\(haveArea.area)")
    } else {
        print("不遵循 Area 協定,無法計算面積")
    }
}

結果如下:
https://ithelp.ithome.com.tw/upload/images/20180118/20107701dTXYA0mxDf.png


可選協定要求

你可以定義對協定的可選要求,這些要求不一定要按照協定的類型來實現,可選要求使用 optional 修飾符作為前綴放在協議的定義中。可選要求允許你的代碼與Objective-C操作,協定和可選要求都必須用 @objc 屬性標記。注意 , @objc 協議只能被繼承自 Objective-C 類或其他 @objc 類採納。它們不能被 struct 或者 enum 採納。

當你在可選要求中使用方法或屬性是,它的類型會自動變成可選項。舉例說,一個 ( Int ) -> String 類型的方法會變成 ( ( Int ) -> String )? 。注意是這個函數類型變成可選項,不是指方法的返回值。

import Foundation
//這邊發現必須 import Foundation 才能使用可選協定,好像是因為可選協定與 Objective-C 程式語言有關係,而 Objective-C 大量使用到 Foundation 的功能 所以需要 import,調查中...。

@objc protocol OptionalProtocol {
    @objc optional func add() -> Int
    @objc optional func sub() -> Int
}

下面我們新增 a、b 兩個變數,並新增一個遵循 OptionalProtocol 的 Class - Math ,裡面我們只定義一個 abb() 的方法,就算我們沒有定義 sub() ,因為是這兩個 functions 在協議中都是可選類型,因此可以不必編寫,也不會編輯錯誤:

var a = 5
var b = 10

class Math:OptionalProtocol {

    @objc func add() -> Int {
        return a + b
    }
    
    // 注意,這邊我們沒有編寫 sub()方法
}

新增一個類型為可選協定 OptionalProtocol 的實例,因為類別 Math 有遵循這個協定 所以可以指派為這個類型的實例,下面再使用 if let 來判斷裡頭是否有值,如果值為 nil,則不會印出結果:

var math:OptionalProtocol = Math()

if let result = math.sub?() {
    print(result)
}

if let result = math.add?(){
    print(result)
}

結果如下:

https://ithelp.ithome.com.tw/upload/images/20180118/20107701L5U4gmgb0t.png


協定擴展

協定可以通過擴展來提供方法和屬性的實現以遵循類型。這就允許你在協議自身定義行為,而不是在每一個遵循或者在全局函數裡面定義。例如我們簡化上面的範例,只留下 add ,並且我們在 Math 協議中添加一個 printer() 方法:

var result = 0
var a = 5
var b = 10

protocol Math {
    func math()
}

class Add:Math {
    func math() {
        result = a + b
    }
}

擴展方法:

extension Math {
    func printer() {
        print("a + b 結果為:\(result)")
    }
}

之後我們也不用在 Add 中定義 printer() 方法,因為擴展協議之後 Math 就提供了我們 printer() 方法:

let someAdd = Add()
someAdd.math()
someAdd.printer()

結果如下:
https://ithelp.ithome.com.tw/upload/images/20180118/20107701GgyCFTwqwP.png


提供默認實現

你可以使用協議擴展來給協議的任意方法或者計算屬性要求提供默認實現。如果遵循類型給這個協議的要求提供了它自己的實現,那麼它就會替代擴展中提供的默認實現。

例如我們下面新增一個 DefaultProtocol 之後,我們在擴展了這個協議,在裡面定義一個 msg 變數,類型為 String,並在他沒有提供的時候印出我們的默認值:

protocol DefaultProtocol {
    var name:String {get}
}

extension DefaultProtocol {
    var msg:String {
        return "默認字串"
    }
}
class Msg:DefaultProtocol {
    var name = "Jeremy"
}

let defaultTest = Msg()
defaultTest.name
defaultTest.msg

結果為下:
https://ithelp.ithome.com.tw/upload/images/20180118/20107701Suh3PEkou0.png



上一篇
Day-29 Swift 語法(25) - 協定 Protocol
系列文
Swift 菜鳥的30天30

尚未有邦友留言

立即登入留言