Delegation實際範例


實際使用時的語法結構

白鬍子教授在Developing iOS 8 Apps with Swift (Stanford)的第6課Delegation & Gestures中,示範了delegation(他用的是data source,與delagate意思相同但主要用於資料部份)的用法與使用時機,這是真正用Delegation的語法結構:

View.swift檔案

protocol FaceViewDataSource: class {
    func smilinessForFaceView(sender: FaceView)-> Double?
}

class FaceView: UIView {
    weak var dataSource: FaceViewDataSource?
    //...
    let smiliness = dataSource?.smilinessForFaceView(self) ?? 0.0
}

Controller.swift檔案

class HappinessViewController: UIViewController, FaceViewDataSource{

    @IBOutlet weak var faceView: FaceView!{
        didSet {
            faceView.dataSource = self
        }
    }

    //...
    func smilinessForFaceView(sender: FaceView)-> Double? {
        return Double(happiness-50)/50
    }
}

這幾段程式碼寫來輕鬆,實際上是很有深度的語法。以下分幾個部份來說說。

白鬍子教授應該稱為Paul Hegarty教授,是一位長期教iOS開發的Stanford軟體工程教授,曾經參與NextSTEP開發(Mac OSX與iOS的前身),在網路上關於他的背景資料並不多。

弱(weak)參考

protocol的後面有個:class,強制這個protocol只能讓class來遵守(conform)它,主要是為了配合下面這段的weak關鍵字。這是為了要使用弱(weak)參考,以避免Retain cycles(或strong reference cycles)。只有class才能使用弱(weak)參考,structs和enums都是value type,只有強(strong)參考。Retain cycles可以參考最後面的參考資料連結。

這一段的語法在Swift的Beta版本到目前已經有變動,網路上有些查到的資料是舊版的,白鬍子的語法是現行版本可用的。

weak var dataSource: FaceViewDataSource?

在Apple的SnakesAndLadders範例中,一樣有使用了Delegate,但卻不是用這種嚴格的寫法,那又是為何?難道不會有Retain cycles(或strong reference cycle)嗎?答案是沒有,那段範例沒有這個問題,那段只是單純的兩個物件實例的互動關係而已。

但是在MVC的實際語法中,我們常會用var delegate = self或用self傳入protocol方法中的sender值,此時就存在Retain cycles(或strong reference cycle)的"風險" - 「彼此互相保持住參考造成循環」,結果就是打結了。雖然Swift語言中並不需要作記憶體管理,也不會讓你作。這樣的寫法是為了避免"風險",所以變成一種通則的語法樣版。像下面這樣:

protocol ProtocolNameDelegate: class {
    // Protocol stuff goes here
}

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Optional值

delegate是weak之外,還是個optional(弱(weak)參考必定為optional類型。),這是代表它"可能"不存在,有可能有,也有可能沒有。白鬍子教授的例子是在protocol裡的方法,連回傳值也是optional的,這連帶會使用到optional chain的語法,以及像??(Nil Coalescing Operator)為了處理optional值敘述,呼應所謂的View與Contoller間的"盲溝通(blind communication)"的說法,因為"瞎了",有沒有其實也要"摸一摸"才知道:

let smiliness = dataSource?.smilinessForFaceView(self) ?? 0.0

雖然它這個例子中的sender還沒有用途,不過這個寫法是要保留一個彈性,可以由sender知道是由哪個實例正在建立委託的。

實作順序

protocol與建立委託參考的View寫在同一個檔案裡,這個實作(撰寫)的順序是:

  1. 在View檔案(或建立委託參考的類)建立一個藍圖或原型。protocol的宣告也在這個檔案裡面。
  2. 在Controller檔案裡,Controller遵守(conform)這個protocol。在Controller裡實作所需的方法。
  3. hook的時機:在Controller建立一個連接View的Outlet(出口),用didSet的property observer方式,指定View實例的delegate(data source)為self(Conctroller:「就是偶啦!」)

第1點是最常讓人搞混的,protocol寫在這個檔案(View)裡,卻並不是在這個檔案中實作。你可以把它當作是一個類型(Type)的宣告比較合理,它是讓這個檔案裡的View,也就是要建立委託參考的類,能用於建立一個這種類型的參考,並呼叫protocol裡的方法,傳入self到方法的sender參數中。

參考資料

Developing iOS 8 Apps with Swift (Stanford)

Protocols (Apple)

Use Weak References to Avoid Retain Cycles

How can I make a weak protocol reference in 'pure' Swift (w/o @objc)