delegate(代理機制)和 protocol(協議)

法蘭克的 iOS 世界
7 min readJan 24, 2017

在 iOS 的世界裡,不管 Objective-C 或 Swift,物件與物件溝通的方式盡可能的透過 delegate(代理機制)和 protocol(協議)的方式來實作,當然也不是只有這個方法可以達到,但是既然我們寫的是 iOS,我們盡可能還是遵循該語言的使用習慣來實作會比較好,我為什麼會這樣說呢?這其實是有原因的,不管 Apple 自家或者第三方所開發的 SDK 只要有關於 delegate 幾乎都是使用此方式所撰寫的。在開始說明之前法蘭克先畫了一個示意圖來說明 delegate(代理) 和 protocol(協議),往後只要有關 delegate 的議題,腦中想著這張藍圖和最常用的 TableView,相信就很容易懂的。

delegate diagram

▲上圖可以這樣理解,ObjectA 透過 ObjectB protocol 來代理 ObjectB,ObjectB 透過它自己的協議(ObjectB protocol)來呼叫 ObjectA 所遵從的協議函式。這樣說似乎有點太文言了,講白話一點就是,我(ObjectA)透過 Delegate 並遵從你的協議書(ObjectB protocol)上的條款(func)來代理你的事情,你(ObjectB)如果要請我做事情,請依照你(ObjectB)協議書(ObjectB protocol)上面的條款(func)來叫我做事情。

當我們在開發專案時,當物件多到炸時,突然不知道哪個物件需要宣告 protocol,法蘭克是這樣記的,給大家參考

當某某物件需要藉由另一物件來 handle 它的事件或需要取得另一物件的資料來呈現 UI 時,就需要替它宣告 protocol

套用在如圖的 ObjectB 的角度來看,它需要 ObjectA 透過代理的機制來 handle 它的事件,所以這時侯就得要幫 ObjectB 宣告一 protocl 為 ObjectBDelegate。如果以上還沒辦法加深印象,以下也是參考的方向。

  • 想像著 TableView 的 delegate 和 dataSource,它們也都是 delegate。因為 TableView 需要另一物件來 handle 它的事件和取得要呈現 UI 的資料,所以就需要宣告 TableViewDelegate 和 TableViewDataSource
  • 自定義的 TableViewCell 也是一個例子,例如在實務上常常會要在 TableViewCell 上添加按鈕,這時侯點擊按鈕就需要通知代理人來處理某些事情,所以 TableViewCell 需要其它物件來 handle 它的事件,故它需要宣告 TableViewCellDelegate

以上有了 delegate 和 protocl 的觀念後,該是動手做做簡單的範例來加深印象。先說明一下該範例總共有三個物件分別是 PageA、PageB、PageBDelegate。PageA 上面有一個按鈕,點擊之後會切換至 PageB,PageB 上有一 TableView,點擊 TableView 的 cell 時會透過 PageBDelegate 將資料帶回 PageA。在開始前先看一下結果會是如何。

▼新增一個 .swift 檔並命名為 PageBDelegate.swift

我們必須得遵照 Apple 的命名規則,通常 delegate 都會是 delegate 結尾的。

▼建立 protocol 並在其底下新增一個沒有主體的 func

該 func 的第一個參數是頁面 B,目前會錯的原因是因為還沒有建立 PageB,沒關係先不要理它一步一步慢慢的往下做。

protocol PageBDelegate {func pageA(_ pageB: PageB, didSelectData data: String)}

到這邊已經建置好 protocol,接著要建立該範例的畫面和元件了。建立畫面的流程法蘭克會快速的帶過,因為這次的重點不在這,而是在 protocol 和 delegate。

▼在 AppDelegate.swift 下建立 UINavigationController,並設定 window 的rootViewController 為 UINavigationController

▼建立 PageA 並在 viewDidLoad 下產生切換至 PageB 的按鈕

▼遵從協議沒有主體的方法

今天的重頭戲來了,要透過 delegate 的機制來達成 PageA 和 PageB 之間的溝通,首先要先將 PageA 實作剛剛所定義的 protocol,在這邊我們透過「,」來遵從它。

class PageA: UIViewController, PageBDelegate {}

這時侯編譯器會顯示錯誤,原因是因為我們還沒有實作 protocol 的方法和點擊切換至 PageB 按鈕時所會觸發的方法。

實作 protocol 的方法和提示訊息的方法(alertErrorMsg)

// 點擊畫面B的cell會觸發的delegatefunc PageB(_ pageB: PageB, didSelectData data: String) {     self.alertErrorMsg(errMsg: data)}// 提示訊息func alertErrorMsg(errMsg: String) {     let alertController = UIAlertController(title: “提示”, message:      errMsg, preferredStyle: .alert)     let confirm = UIAlertAction(title: “確定”, style: .default, handler: nil)     alertController.addAction(confirm)     self.present(alertController, animated: true, completion: nil)}

加上後還會有錯是因為還沒建立點擊按鈕的觸發事件(onClickToPageB),我們先跳過這裡直接先來實作 PageB。

▼建立 PageB.swift

在這邊我們會建立一個 UITableView 來顯示資料,並宣告 delegate 的變數,該變數非常重要!它是這次的重點,沒有它則休想使用 delegate 的機制。可以把它想像成它是 PageA 和 PageB 溝通的橋樑。另外產生 UITableView 的過程不是這次的重點,就不多加贅述了。

這邊要來說明一下 UITableView 的 protocol PageA func。當我們在 PageB 點擊 cell 的時侯這個 func 則會被觸發,第 50 行則是會透過 delegate 的機制去觸發 PageA 的 PageA didSelectedData 方法並將所選的資料帶回去。到此我們已經實作完 PageB,最後回到 PageA 將點擊按鈕的觸發的事件加上去就大功告成了。

▼在 PageA 建立切換至 PageB 的按鈕觸發事件

因為第 4 行的關係使這個 func 看起來並不單純,第 4 行要表達的意思是將自己指向 PageB 的 delegate 這個變數,用意是在 PageB 使用 self.delegate.PageB(self, didSelectedData: self.dataList[indexPath.row]) 才能呼叫的到 PageA 的方法。到此該範例已完成,試著執行看看有沒有像法蘭克的影片一樣。

結論

看似複雜的 Delegate 機制,其實一點也不,只要在腦中只要遵循以下步驟即可:

  1. 宣告 PageB 的 protocol 為 PageBDelegate。
  2. 在 PageB 宣告全域變數 delegate,型別為上一步驟產生的 protocol。
  3. 在 PageA 遵從 PageB(pageB.delegate = self) 並實作 PageBDelegate protocol。

如果您喜歡我的文章,請多按幾下「拍手」給我鼓勵,或是按「follow」讓我持續提供好文章給您

--

--