本文专门讲解 iOS 响应者链的构成与传递:
nextResponder(API 中为next)的规则、链的路径示意、事件沿链传递与转发方式,以及如何通过重写next修改链。与「事件如何找到目标」配合阅读可参见 02-hitTest 与事件传递详解。
一、响应者链是什么
响应者链是由 UIResponder 的 next(文档与习惯上常称 nextResponder)串联起来的一条链。当第一响应者不处理某事件时,系统会把该事件传给 next,再传给 next.next,直到某个响应者处理或链结束(next == nil)[1]。
对触摸事件而言,第一响应者通常是 hit-test 得到的视图;对按键、摇动、编辑菜单等,第一响应者多为当前获得焦点的对象(如 UITextField)。
二、nextResponder 的构成规则
各子类对 next 的实现决定了链的走向。以下为 UIKit 中的典型规则 [[1]][2]:
| 当前响应者 | next 通常为 |
|---|---|
| UIView | 若该 view 是某个 UIViewController 的根 view(viewController.view),则 next 为该 UIViewController;否则为 superview |
| UIViewController | 若该 VC 的 view 是某 window 的根 view,则 next 为 UIWindow;若该 VC 被其他 VC present,则 next 为 presenting ViewController |
| UIWindow | UIApplication |
| UIApplication | App Delegate(仅当 delegate 是 UIResponder 子类且不是 view、viewController 或 app 本身时) |
| 链尾 | nil |
因此,典型链路径为:
Hit-test View(或 First Responder)
→ 其 UIViewController(若存在)
→ 该 VC 的 view 的 superview
→ … 逐级 superview …
→ Window 的 rootViewController.view
→ rootViewController
→ UIWindow
→ UIApplication
→ App Delegate(若符合条件)
→ nil
三、响应者链路径示意
flowchart TB
subgraph 视图层级
V[Hit-test View]
P[Superview]
R[Root View]
end
subgraph 链路径
V --> VC[UIViewController]
VC --> P
P --> R
R --> VC2[Root VC]
VC2 --> W[UIWindow]
W --> App[UIApplication]
App --> Del[App Delegate]
Del --> nil[nil]
end
简化理解:从被点中的 view 开始,先到管理它的 ViewController(若有),再到该 view 的 superview,一路到 window → application → app delegate,最后到 nil。
3.1 响应者链知识结构(思维导图)
mindmap
root((nextResponder 链))
起点
hit-test view
First Responder
规则
UIView → VC 或 superview
UIViewController → superview 或 window
UIWindow → UIApplication
UIApplication → App Delegate / nil
传递
touchesBegan 等转发
target nil 沿链找 target
修改
重写 next 属性
3.2 泳道图:事件沿链传递的参与方
flowchart LR
subgraph 视图层
V1[Hit-test View]
V2[父 View]
end
subgraph 控制器层
VC[ViewController]
end
subgraph 窗口与应用
W[UIWindow]
App[UIApplication]
Del[App Delegate]
end
V1 -->|不处理则 next| VC
VC -->|next| V2
V2 -->|next| W
W --> App
App --> Del
四、事件沿链传递与转发方式
4.1 触摸事件的传递
触摸事件(touchesBegan / touchesMoved / touchesEnded / touchesCancelled)会先发给 hit-test 得到的视图。若该视图未重写这些方法,UIKit 的默认实现会把事件传给 next,即沿链向上传递 [[2]]。
若视图重写了触摸方法但希望「自己处理一部分,其余交给链上」:需显式调用 next 的对应方法,否则事件不会继续传递。
// 示例:将触摸事件交给下一响应者
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 可选:在此做自己的逻辑(如埋点、高亮)
next?.touchesBegan(touches, with: event)
}
不调用 next?.touchesBegan(...) 则链在此中断,上层视图或 ViewController 不会收到该触摸。
商用场景示例:自定义 Cell 内的某个 view 只做展示,希望点击「透传」到 Cell 或 VC 做统一处理(如整行点击跳详情);在自定义 view 中重写 touchesEnded 并调用 next?.touchesEnded(touches, with: event),由上层决定是否跳转。
延伸:除系统 next 链外,业务层还常用 Delegate、Block/闭包、函数封装、快速枚举/for 循环 等方式做事件或回调的传递,与「链」形成互补。详见 06-响应者链传递方式与编程模式详解。
4.2 Action 与 target-nil
UIControl 的 target-action 中,若 target 为 nil,系统会从当前第一响应者开始,沿 next 查找第一个实现了对应 action 的响应者并调用,即 action 沿响应者链查找 target [[1]]。编辑菜单(复制/粘贴等)也利用该机制在链上查找能执行 copy(_:)、paste(_:) 等的对象。
五、修改响应者链:重写 next
可通过重写 next 属性改变链的走向,使事件或 action 先经过指定对象 [[1]]。
// 示例:让某自定义 view 的 next 指向指定的 responder(如父 VC)
override var next: UIResponder? {
return parentViewController ?? super.next
}
这样,当该 view 不处理事件时,事件会先传给 parentViewController,再按 parentViewController.next 继续。适用于希望「某视图的上级一定是某个 VC」的场景。
商用场景示例:列表页中某个自定义 view(如卡片内嵌的容器)希望点击后一定由当前页的 ViewController 处理(如弹窗、路由),而不经过中间若干 superview;通过重写 next 指向 VC,可保证 Action 或 touches 先到达 VC。
遍历响应者链的调试/工具代码(Swift):
extension UIResponder {
/// 打印从当前节点到链尾的整条响应者链,便于调试
func printResponderChain() {
var current: UIResponder? = self
var level = 0
while let r = current {
print(String(repeating: " ", count: level) + "\(type(of: r))")
current = r.next
level += 1
}
}
}
// 使用:在某个 view 或 VC 中调用 self.printResponderChain()
六、与 hit-test 的关系小结
| 阶段 | 方向 | 作用 |
|---|---|---|
| Hit-Testing | 自顶向下 | 确定「谁被点中」→ 该 view 为触摸的 first responder |
| Responder Chain | 自底向上 | 从该 view 开始,沿 next 传递,直到有人处理或链结束 |
两者配合:hit-test 决定链的起点,next 决定链的走向与传递顺序。详见 01-总纲、02-hitTest 与事件传递。业务中「谁来处理」除依赖链外,还可通过 06-响应者链传递方式与编程模式详解 中的 Delegate、Block、函数封装、遍历传递等模式实现。
参考文献
[1] Using responders and the responder chain to handle events - Alter the responder chain
[2] UIResponder - Managing the responder chain (next)