使用 Swift 開發 iOS App 時,相信大家都曾疑惑過:明明按下的是按鈕,為什麼會是這個視圖控制器收到事件?
深入追蹤觸控事件在畫面中如何流動,就會發現底層其實採用了責任鏈模式。
先說結論,UIResponder 鏈是 Apple 在框架層級實作責任鏈模式(Chain of Responsibility)的代表案例,而責任鏈模式是 GoF 設計模式之一。responder 會逐一將責任傳給下一個 responder,直到找到能處理事件的物件。
本文會拆解程式碼,整理責任鏈模式的概念、UIResponder 鏈的實際運作方式,以及理解這些內容對實務開發的幫助。
先看重點摘要
- 責任鏈模式是一種設計模式,會讓多個物件像鏈條一樣傳遞請求,直到找到能處理請求的物件。
- iOS 的 UIResponder 鏈,就是在框架層級實作這個模式。
- 觸控事件會依序從 UIView 傳到上層視圖、視圖控制器、視窗,最後到應用程式。
- next 屬性指向鏈中的下一個環節;如果沒有人處理事件,事件就會被直接丟棄。
什麼是責任鏈模式?
責任鏈模式是一種行為型模式,能讓請求傳送端與處理端保持鬆散耦合。
簡單來說,就像公司裡送簽核一樣。助理經理能處理就到此為止,不能處理就交給經理,經理也無法處理時再往上交給協理。
提出請求的人不必知道最後由誰核准,只要把請求交給鏈上的第一個環節,它就會自行往下傳遞。
這個模式的核心是,每個處理物件只需要知道兩件事:自己能不能處理這個請求,以及不能處理時下一個物件是誰。
用 Swift 極度簡化後,大致會是這樣的骨架。
class Handler {
var next: Handler? // 鏈中的下一個環節
func handle(_ request: Int) {
// 無法處理時將責任傳給下一個物件
next?.handle(request)
}
}
只靠一個 next 屬性串起鏈條,無法處理時就交給 next,這個結構原封不動地整合在 UIResponder 中。
UIResponder 鏈如何運作?
在 iOS 中,所有能接收觸控、動作、遠端控制等事件的物件,都會繼承 UIResponder。UIView、UIViewController、UIWindow 和 UIApplication 全都是 UIResponder 的子類別。
因此,這些物件各自都有一個屬性,而它的正確名稱就是 next。這個屬性定義在 UIResponder 中。
// UIResponder中定義的屬性
var next: UIResponder? { get }
使用者觸碰畫面時,系統會先找出最內層、實際被觸碰的視圖。這稱為命中測試,也會在此時決定 first responder 候選物件。
如果該視圖沒有處理事件,系統就會沿著 next 向上傳遞,大致順序如下。
- UIView(被觸碰的視圖)
- 上層的各個超級視圖
- 管理該視圖的 UIViewController
- UIWindow
- UIApplication
- UIApplicationDelegate
即使一路走完整條鏈都沒有人處理事件,事件也只會無聲地被丟棄。App 不會當掉或產生錯誤,只是單純忽略它。
有趣的是,視圖的 next 不一定永遠是超級視圖。
如果該視圖是視圖控制器的根視圖,next 就會是視圖控制器,而不是超級視圖。
了解這點對實務有什麼好處?
老實說,一開始即使不了解這個內部結構,App 也能正常運作。但理解之後,除錯方式確實會不一樣。
分享一個我遇過的案例:自訂視圖中的點按手勢沒有反應。
依序追蹤事件在哪個 responder 停下來後,很快就找到原因:上層視圖的 isUserInteractionEnabled 是 false,責任鏈因此在那裡中斷。
自訂事件傳遞也很實用。你可以自行定義沿著 UIResponder 鏈向上傳遞的訊息,讓深層視圖產生的動作不必透過 delegate,就能傳到上層視圖控制器。
收起鍵盤時常用的 endEditing(true),其實也運用了這條鏈。它會找到 first responder,並呼叫 resignFirstResponder。
總結來說,理解 UIResponder 鏈後,遇到事件沒有反應時,就能有邏輯地追蹤中斷位置,也能避免濫用 delegate,設計出將事件向上傳遞的架構。
常見問題
Q. first responder 和 responder 鏈是不同的東西嗎? A. first responder 是唯一有資格優先接收事件的物件;responder 鏈則是從 first responder 向上延伸的整條鏈。
Q. next 一定指向超級視圖嗎? A. 不一定。一般視圖會指向超級視圖,但視圖控制器的根視圖,其 next 會是視圖控制器。
責任鏈模式這個名稱看起來可能很難,但核心其實只有一個簡單原則:無法處理時就交給下一個物件。希望本文能幫助你理解 UIResponder 鏈這個 iOS 隱藏的骨架。下次遇到事件沒有反應的 Bug,請試著逐個環節追蹤這條鏈。

