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:方法。对于动作消息和事件消息,响应者链路也是有区别的,这一点需要我们仔细甄别。

谢谢大家!

相关推荐
超级小忍13 小时前
从零开始:JDK 在 Windows、macOS 和 Linux 上的下载、安装与环境变量配置
java·windows·macos
三劫散仙14 小时前
mac m1上使用Kerberos访问远程linux hadoop集群的正确姿势
linux·hadoop·macos
@蓝眼睛14 小时前
mac的m3芯片安装JDK8、JDK17
macos·jdk
彬彬醤14 小时前
Mac怎么连接VPS?可以参考这几种方法
大数据·运维·服务器·数据库·线性代数·macos·矩阵
ka2x1 天前
Mac 电脑 IDEA 执行 Maven 出现 No route to host 问题
macos·maven·intellij-idea
易我数据恢复大师1 天前
如何在mac玩windows游戏?3个工具推荐,不用换电脑!
macos·mac·mac运行windows游戏·easeus os2go·mac玩windows游戏
@蓝眼睛1 天前
mac的m3芯片通过Homebrew安装git
git·macos
网络研究院2 天前
新的 SHAMOS MacOS 窃取程序利用单行终端命令攻击用户
macos·攻击·漏洞·用户
@蓝眼睛2 天前
mac的m3芯片安装mysql
mysql·macos
泓博2 天前
iOS打开开发者模式
macos·objective-c·cocoa