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

谢谢大家!

相关推荐
空の鱼1 小时前
Mac 上 Node 版本管理(brew + nvm 共存方案)
macos·node.js
末代iOS程序员华仔5 小时前
iOS个人开发者账号申请为啥会被卡住
ios·swift·figma
你疯了抱抱我5 小时前
【MacOS】自己开发的APP,dock图标圆角 + 调整大小(py2app 版)
macos
k4m7v2pz7 小时前
Apple Silicon Mac 上用 Wine 跑《重武器老鼠》:从下载到进游戏全流程
macos·wine·老游戏·rosetta 2·mighty rodent·腾讯软件中心
chunmiao303220 小时前
苹果首款2nm芯片M6与M5 Ultra发布,Mac本地跑大模型进入新阶段
macos
denggun123451 天前
信号量(DispatchSemaphore vs AsyncSemaphore)、swift协作式线程池 and python信号量
开发语言·python·swift
guo_wen_qiang1 天前
mac中iTerm有没有类似xshell远程连接服务器的用户名密码方式,不用每次都要输入用户密码
运维·服务器·macos
2501_915918411 天前
Swift IDE 有哪些?按平台和需求选择Swift 开发工具
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
HySpark1 天前
语音分段策略优化实践:如何解决错误分段引发的级联误差
ide·macos·语音识别·xcode·熙瑾会悟
AirDroid_cn1 天前
Mac连不上Apple TV?超级终端识别失败排查指南
macos