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

谢谢大家!

相关推荐
0wioiw07 小时前
Onesignal(Xcode)
ide·macos·xcode
哈基米~南北绿豆9 小时前
虚拟机体验:在Windows/Mac上运行鸿蒙PC开发环境
windows·macos·harmonyos
大熊猫侯佩16 小时前
Swift 6 驱魔实录:揭开 Combine 与 @Sendable 的“血色契约”
swift·block·combine·preconcurrency·sendable·mainactor·isolation
初级代码游戏17 小时前
iOS开发 SwiftUI 15:手势 拖动 缩放 旋转
ios·swiftui·swift
2601_9491465319 小时前
APP语音通知接口集成实战:移动端应用接入语音提醒API的开发手册
macos·objective-c·cocoa
ujainu19 小时前
Flutter + OpenHarmony 游戏开发进阶:虚拟摄像机系统——平滑跟随与坐标偏移
开发语言·flutter·游戏·swift·openharmony
小鹿软件办公21 小时前
Apple 发布 macOS 11、watchOS 10 和 watchOS 9 更新
macos·objective-c·cocoa
chao_7892 天前
双设备全栈开发最佳实践[mac系统]
git·python·macos·docker·vue·全栈
2501_915921432 天前
不用 Xcode 上架 iOS,拆分流程多工具协作完成 iOS 应用的发布准备与提交流程
android·macos·ios·小程序·uni-app·iphone·xcode
Ron丶2 天前
iOS 旧版本 App 下载方法汇总:如何获取历史版本 IPA(2026 仍有效)
windows·经验分享·macos·ios·电脑