Cocoa: Understanding Responder Chain

最近给 CurrencyX 添加手势滑动返回遇到一些问题,特地复习了一下 Responder Chain 的内容,本文翻译并梳理了 Cocoa Event Handling Guide 中的 Responder Chain 部分。原文:The Responder Chain

定义

在 App 中发生的 Event 或者 Action Message 将会在 Responder Chain 中传递并得到处理,Responder Chain 指的是按顺序连接的一些 Responder Objects。由 First Responder 开始,对于接受到的 Message,Object 对其进行处理或者传递给 Next Responder。Responder 可以调用 delegate 方法进行事件处理。Application Kit 将自行创建 Responder Chain,我们也可以通过 setNextResponder: 方法来自定义 Responder Chain。

一个应用汇总可能有多个 Responder Chain,但在某一时刻只有一个 Responder Chain 是 Active 的。对于不同的 Event 或者 Action Message,Responder Chain 也是不同的。接下来将具体说明。

Event Messages 的 Responder Chain

大部分 Event Message 将会传递给 Event 所发生窗口的 Responder Chain,NSWindow 根据事件类型不同来决定 Chain 的起点。一般情况下:

  • Key Event(键盘事件)以 Window 的 First Responder 作为起点;
  • Mouse or Tablet Event 以用户触发的 View 作为起点。

从起点开始,如果事件没有特殊处理,则会沿着 View Hierarchy 向上传递至 Window;一般来说,Window 的 First Responder 将是 Window 内当前“选中”的 View,Next Responder 则是 Superview。如果 Window 由 NSWindowController 来管理,Window Controller 将成为 Final Next Responder。在 NSView 之间,可以插入其他的 Responder。

如果 Final Responder 仍然没有对事件做出处理,将会调用 noResponderFor: 方法,对于键盘事件系统将发出 beep 提示音。Responder 可以重写该方法来提供额外的处理。

Action Messages 的 Responder Chain

Application Kit 对于处理 Action Message 的 Responder Chain 构建比较复杂,根据以下两种情况作出了划分:

Application 是否基于 Document Architecture,如果不是,那么它是否为 Window 提供了 NSWindowController;
Application 当前的 Key Window 是否是它的 Main Window。

在应用运行时, Action Message 需要更灵活的 Runtime Mechanism 来决定它的 Target,因此构建 Responder Chain 的方法也更加精细。 和 Event Message 一样,Action Message 不一定在单一的 Window 中得到处理。

Non-document-based

根据 Key Window 和 Main Window 是否为同一 Window;Window 是否由 Window Controller 管理分为几种情况讨论。

Key Window 即 Main Window

最简单的情况是,为一个单一窗口(Key Window 即 Main Window)的 Non-document-based Window 构建 Responder Chain:

  1. 以 Main Window 的 First Responder 为起点,按照 View Hierarchy 构建的 Responder Chain;
  2. Main Window 本身;
  3. Main Window 的 delegate(并不要求继承自 NSResponder);
  4. Application Object,即 NSApp;
  5. Application Object 的 Delegate (并不要求继承自 NSResponder)。

为单一窗口 Non-document-based 的 Application 处理 Action Message 构建的 Chain 如下图所示:

NSWindow 和 NSApplication 默认都将事件传递给 delegate 处理,而 delegate 并不属于 Responder Chain(Window 或 Application 的 nextResponder 并不返回它们的 delegate)。

Key Window 与 Main Window 不同

当 Application 的 Key Window 和 Main Window 并不是一个 Window 时,两个 Window 中的 Responder 共同组成了 Action Message 的 Responder Chain。首先,Key Window 的 Responder Chain 对 Action Message 作出相应,接着传递至 Main Window 的 Responder Chain:

  1. 以 Key Window 的 First Responder 为起点,按照 View Hierarchy 构建的 Responder Chain;
  2. Key Window 本身;
  3. Key Window 的 delegate(并不要求继承自 NSResponder);
  4. 以 Main Window 的 First Responder 为起点,按照 View Hierarchy 构建的 Responder Chain;
  5. Main Window 本身;
  6. Main Window 的 delegate(并不要求继承自 NSResponder);
  7. Application Object,即 NSApp;
  8. Application Object 的 Delegate (并不要求继承自 NSResponder)。

从第三阶段开始与以 Main Window 的 Responder 开始构建的 Responder Chain 相同。

Window 由 NSWindowController 管理

为由 Window Controller 管理的 Window 构建 Responder Chain 不同之处是在 Window 和 Window Delegate 之间插入了 Window Controller:

  1. 以 Main Window 的 First Responder 为起点,按照 View Hierarchy 构建的 Responder Chain;
  2. Main Window 本身;
  3. 管理 Main Window 的 NSWindowController(继承自 NSResponder);
  4. Main Window 的 delegate(并不要求继承自 NSResponder);
  5. Application Object,即 NSApp;
  6. Application Object 的 Delegate (并不要求继承自 NSResponder)。

为 Non-document-based 并由 NSWindowController 管理的 Application 处理 Action Message 构建的 Chain 如下图所示:

Document-based

对于 Document-based Application,默认为 Main Window 构建的 Responder Chain 在 Window Delegate 和 NSApp 间插入了 NSDocument:

  1. 以 Main Window 的 First Responder 为起点,按照 View Hierarchy 构建的 Responder Chain;
  2. Main Window 本身;
  3. 管理 Main Window 的 NSWindowController(继承自 NSResponder);
  4. Main Window 的 delegate(并不要求继承自 NSResponder);
  5. NSDocument Object(与 Window 的 delegate 不同);
  6. Application Object,即 NSApp;
  7. Application Object 的 Delegate (并不要求继承自 NSResponder)。

为 Document-based 的 Application 处理 Action Message 构建的 Chain 如下图所示:

Responder Chain 的其它用途

在 Application Kit 中,除了处理 Event 和 Action 之外,Responder Chain 还有以下用途:

当 Menu Item 的 Target 为 nil 时,会自动变为 Disable。为了判断 Target 是否为 nil,NSMenu 将根据 Menu Object 在不同的 Responder Chain 中进行搜索:

  1. 对于 Application Menu,NSMenu 将在 Full Responder Chain 搜索,如果存在 Object 实现了 Item 的 Action Method,则在 validateMenuItem: 返回 YES;
  2. 对于 Context Menu,NSMenu 将仅在 Context Menu 相关 Window 的以相关 View 为起点的 Responder Chain 中搜索。

Toolbar Item 的 enable 搜索条件与 Menu Item 相同,结果将在 validateToolbarItem: 中返回。

详细参见:Enabling Menu Items 和 Validating Toolbar Items。

Service 可用性

类似的,Services Facility 将在 Full Responder Chain 中传递 validRequestorForSendType:returnType: 消息,通过返回值判断其它 Application 提供的 Service 在当前 Application 中是否可用。

详细参见:Service Implementation Guide

显示错误

Application Kit 将在与上述定义不同的 Responder Chain 中处理 Error 和 Error Presentation,通过 NSResponder 的 presentError:modalForWindow:delegate:didPresentSelector:contextInfo: 和 presentError: 处理。

详细参见:Error Handling Programming Guide

注释: * Main Window:最前的 Document 或 Application Window; * Key Window:当前活跃的 Window(Focus User Input); * Application Menu:App 在系统 Bar 上的菜单栏; * Context Menu:右击 View 时弹出的菜单栏。


支持我们

  • SalesX 是给 Apple 开发者使用的菜单栏工具,第一时间把 app 销售情况推送给你,7 天免费试用
  • CurrencyX 是 Mac 上小而美的汇率 app

如果你觉得文章对你有帮助,可以买一个支持我们。