AppKit 中的响应者链

引言

大家好,我是一牛。记得博主刚入行的时候,那时候 iOS 面试题经常会考响应者和响应者链,不过背一背八股文倒是也不难,但是工作中却很少用到。今天,我就以一个桌面开发者的角度,谈一谈 AppKit 中的响应者和响应者链,探讨一下它们到底有什么用。

响应者

首先我们先介绍下响应者。响应者的定义:

  1. 可以接受事件的对象。
  2. 可以通过响应者链接受事件。
  3. AppKit 的响应者需继承NSResponder, 例如NSWIndow NSView NSWindowController

响应者定义了接收事件消息和动作消息的编程接口。

第一响应者

第一响应者通常是用户使用鼠标或者键盘激活的用户界面。NSWindow 的第一响应者是它自己,我们可以通过它的接口makeFirstResponder(_:)设置当前窗口的第一响应者。第一响应者就是响应者链的第一个响应者对象,接收事件消息和对象消息。

当窗口接受到鼠标按下事件时,它会询问当前的视图对象是否可以成为第一响应者,如果视图对象接受成为第一响应者,那么该视图对象成为第一响应者。NSView默认不接受成为第一响应者,我们可以重写方法acceptsFirstResponder来改变默认行为。点击NSTextField时,第一响应者会变成文本输入框,接收按键输入。当然我们也可以通过键盘来改变第一响应者。

响应者链

响应者链是由一系列相互关联的响应者对象组成的链路,事件或动作消息会沿着该链路传递并处理。当一个响应者无法处理某个特定事件消息或者动作消息,它会将该消息转发给它的继任者nextResponder,如果该响应者仍然不能处理该消息时,它会沿着链路继续向下传递,直到响应者的nextResponder为空。我们可以通过接口nextResponder改变默认的链路或者遍历该链路。

swift 复制代码
// 变量响应者链
var current = view.window?.firstResponder
while current != nil {
    current = current?.nextResponder
}

事件消息中的响应者链

事件消息包括鼠标事件,触摸板事件,以及键盘事件等。

  • 键盘事件的默认响应者链从窗口的第一响应者开始。
  • 鼠标事件的默认响应者链则始于用户事件发生的视图。

若事件未被处理,会沿视图层级向上传递至代表窗口本身的NSWindow对象。第一响应者通常是窗口内"被选中"的视图对象,其下一响应者为它的父视图,依此类推直至NSWindow对象。若窗口由NSWindowController管理,则控制器会成为链路的最后一个响应者。

若最终没有对象处理事件,链末端的响应者会调用noResponderFor:方法。响应者对象可重写此方法以执行自定义逻辑。当我们按下键盘的字母键时,如果响应者链中任何对象都没有实现keyDown,最终系统会调用 noResponderFor:,发出蜂鸣声。

动作消息中的响应者链

为了说明方便,我们使用Xcode创建一个Storyboard的普通应用(非文档应用),这是一个NSWindowController的应用。

在Storyboard上我们给视图控制器的视图增加一个自定义子视图(CustomView )。我们在这个子视图的类中重写了acceptsFirstResponder, 使得它能够成为第一响应者。当我们运行应用并展现窗口时,这个视图会成为当前窗口的第一响应者,并不需要我们手动点击这个视图来切换第一响应者。当然我们也可以编码决定谁成为第一响应者。此时,响应者链路如下:

CustomView -> 控制器的视图 -> 控制器 -> 窗口 -> 窗口控制器

然后我们在控制器视图中添加一个按钮, 只给按钮设置动作消息,不设置目标。

objective-c 复制代码
NSButton *actionButton = [NSButton buttonWithTitle:@"Click Me!" target:nil action:@selector(handleActionMessage:)];
[self.view addSubview:actionButton];

然后我们在CustomView类中添加动作方法

objective-c 复制代码
-(void)handleActionMessage:(NSButton *)sender {
    NSLog(@"CustomView received the action message!");
}

此时如果我们点击按钮,CustomView会收到这个动作消息。这是因为此时CustomView是第一响应者,它会首先处理动作消息。

我们在CustomViewhandleActionMessage:删除, 在ViewController中添加方法

objective-c 复制代码
-(void)handleActionMessage:(NSButton *)sender {
    NSLog(@"ViewController received the action message!");
}

点击按钮,ViewController会收到这个动作消息, 这是因为CustomView没有处理该消息,它会把消息传递给它的下一个响应者。

如何我们将按钮的目标设置为ViewController, 消息处理不会走响应者链,ViewController处理该消息。

objective-c 复制代码
//此时按钮的目标是ViewController
actionButton.target = self;

结语

响应者链作为AppKit的核心机制,其设计体现了"责任链模式"的经典思想。消息会从第一个响应者沿着响应者链传递,直到有响应者处理,或者调用noResponderFor:方法。对于动作消息和事件消息,响应者链路也是有区别的,这一点需要我们仔细甄别。

谢谢大家!

相关推荐
csdn_aspnet2 小时前
在 MacOS 中安装 MySQL 8
mysql·macos
西西弗Sisyphus7 小时前
将用于 Swift 微调模型的 JSON Lines(JSONL)格式数据集,转换为适用于 Qwen VL 模型微调的 JSON 格式
swift·qwen3
木兰不吃草7 小时前
完美解决在最新系统 MacOS 26 无法使用 playCover 键盘映射的问题(下载安装教程,完美适配)
macos·键盘映射·playcover·mac26
songgeb11 小时前
🧩 iOS DiffableDataSource 死锁问题记录
ios·swift
乾坤瞬间13 小时前
【Deepseek OCR】重磅测试,mac环境下的体验【本人已经本地实验成功】
macos·ocr
开发者驿站14 小时前
2025年保姆级C++环境配置教程(Windows/macOS双平台)
c++·windows·macos
猫头虎15 小时前
解决升级IDEA2025.2后,每次打开Maven项目爆红的问题:Windows和Mac解决方案
java·ide·macos·maven·intellij-idea·idea·intellij idea
大熊猫侯佩15 小时前
【大话码游之 Observation 传说】上集:月光宝盒里的计数玄机
swiftui·swift·weak·observable·self·引用循环·observations
白玉cfc15 小时前
【iOS】KVC 与 KVO 的基本了解与使用
macos·ios·objective-c·cocoa
马拉萨的春天16 小时前
谈谈你对iOS的runtime和runloop的了解
macos·ios·cocoa