天天看點

Swift中Protocol的說明和用法

注:來自斯坦福的Swift公開課

協定(Protocol)類似于C++中的一個聲明的類,可基本表達為一個方法或變量的清單,但其中不包含任何的實作(a list of methods and vars with no implemention)。

一個協定由三個部分組成:

  1. 協定的聲明(協定中的屬性和方法。如函數的參數和傳回值,或一個變量的清單)
  2. class、struct或enum對實作這個協定的聲明(要去實作協定中的聲明的人,可以是struct、class或enum)
  3. 上一條中的class、struct或enum中實作的聲明的代碼(這些代碼實作了協定中對變量的處理方法)

在Swift中,一個協定中的所有方法和變量都是強制性的。即如果一個class、struct或enum要實作協定中的方法或變量,則它必須實作協定中的所有方法和變量。但是在OC中不一樣,OC中協定可以由可選方法,可以選擇實作或者不實作協定中的某些方法和變量。在Swift中可以采用在協定的聲明前面添加一個"@ojbc",來表示這是一個OC的協定而不是一個Swift的協定。這樣就可以使得協定中的方法變為可選方法(optional)。

聲明一個協定的方法:

//SomeProtocol是協定的名字,類似于class A
//InheritedProtocol1, InheritedProtocol2是協定所繼承的那些協定
protocol SomeProtocol : InheritedProtocol1, InheritedProtocol2 {
    //當聲明協定中有變量的時候,必須要說明這是一個隻讀變量還是一個可讀寫的變量(get,set)
    var someProperty: Int { get set }
    func aMethod(arg1: Double, anotherArgument: String) -> SomeType
    //協定中的方法如果會修改實作他的struct,則需要标記為mutating
    mutating func changeIt()
    init(arg: Type)
}
           

protocol中的繼承和struct中的繼承不同。protocol中的繼承的意思是,如果聲明要實作SomeProtocol,則必須要同時實作InheritedProtocol1和InheritedProtocol2。

如果确定這個協定一定不會被一個struct所實作,就不需要去标記為mutating,但是必須說明這個協定是一個隻會被class所實作的協定。可以通過将class放在協定聲明後的第一位上,之後這個協定就不能被struct所實作了。例如:

protocol SomeProtocol : class, InheritedProtocol1, InheritedProtocol2 {
    var someProperty: Int { get set }
    func aMethod(arg1: Double, anotherArgument: String) -> SomeType
    func changeIt()
    init(arg: Type)
}
           

可以在一個協定當中加上一個初始化(initializer),意味着如果一個class或struct要實作這個協定,它就必須實作這個初始化。

如果一個class或struct要實作一個protocol,隻需要在它的父類後面加上它所要實作的protocol(對于struct來說沒有父類,隻用在後面加上冒号和protocol)。如果聲明了要實作一個protocol,但是沒有實作裡面的方法和變量,編譯器就會報錯。一個struct或class可以實作任意多的protocol。如果在協定中有初始化(init),則必須要在class或struct中标注required。防止一個子類繼承了這個類并且繼承了那個初始化,則繼承自父類的初始化就會失效。如果标注了required,則子類不會再去實作這個協定,隻需要繼承父類實作的協定就可以。當一個類實作了一個協定,他的所有子類也能夠實作這個協定是以所有的協定的初始化都要标注required。

struct和class中不需要将所實作的protocol中的方法和變量放在自己的結構體或類的聲明中,隻需要将他們放在extension中。

protocol的使用方法:

protocol Moveable {
    mutating func move(to point: CGPoint)
}

class Car : Moveable {
    func move(to point: CGPoint) {}
    func changeOil()
}

struct Shape : Moveable {
    mutating func move(to point: CGPoint) {}
    func draw()
}

let prius: Car = Car()
let square: Shape = Shape()
//可以将prius指派給Moveable型變量thingToMove,因為prius實作了Moveable協定。
var thingToMove: Moveable = prius
//thingToMove不是Car類型的變量,而是Moveable型變量,是以可以調用.move()
//但是不能調用Car中的方法,盡管prius是Car類型的變量并且實作了Car中的方法
thingToMove.move(to:...)
thingToMove.changeOil() //報錯
//可以将square指派給thingToMove,因為square是Shape型,而Shape實作了Moveable類
thingToMove = square
//可以聲明一個Moveable類型的數組,裡面包含prius和square,即使這是兩個不同類型,但是他們都實作了Moveable
let thingsToMove: [Moveable] = [prius, square]
           

同樣protocol類型的變量可以作為函數的參數,例如:

func slide(slider: Moveable) {
    let positionToSlideTO = ...
    slider.move(to: positionToSlideTO)
}

slide(prius)
slide(square)
           

函數的參數也可以是一個實作了多個協定的參數,例如:

func slipAndSlide(x: Slippery & Moveable)
slipAndSlide(prius) //報錯,因為prius雖然實作了Moveable,但是沒有實作Slippery
           

在這個函數中參數x必須同時實作Slippery和Moveable兩個協定。