
第一个例子和第二个例子有什么区别?
目标负责人是什么?
在哪种情况下,单击按钮时会调用该方法?
TL; DR
当单击按钮时,在两种情况下都会调用我们的方法。
仅在第一个示例中, UIKit会尝试在分配的目标(我们有ViewController )中调用该方法。 如果此方法不存在,它将崩溃。
在第二个示例中,使用iOS Responder Chain, UIKit将查找具有此方法的最近的UIResponder -a。 如果找不到我们的方法,就不会崩溃。
UIViewController, UIView, UIApplication继承自UIResponder 。
iOS Responder Chain及其内幕
UIKit iOS Responder Chain过程由UIKit处理, UIKit与UIResponder的链表动态配合使用。 此UIKit列表UIKit从第一个响应者(注册事件的第一个UIResponder创建的,我们有UIButton(UIView)及其subviews 。

UIKit会遍历UIResponder列表,并使用canPerformAction检查我们的功能。
open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool
如果所选的UIResponder无法使用特定方法,
UIKit使用返回下一个UIResponder的target方法将操作递归发送到列表中的下一个UIResponder 。
open func target(forAction action: Selector, withSender sender: Any?) -> Any?
重复此过程,直到其中一个UIResponder可以使用我们的方法或列表结束并且系统忽略此事件为止。
在第二个单击示例中,它是由UIViewController处理的,但是由于UIKit是第一个响应者,因此它首先向UIView发送了一个请求。 它没有必需的方法,因此UIKit将操作重定向到链接列表中的下一个UIResponder ,后者是具有所需方法的UIViewController 。
在大多数情况下, iOS Responder Chain是一个简单的subviews列表,但是可以更改其顺序。 您可以使UIResponder (becomeFirstResponder)成为
第一个UIResponder并使用resignFirstResponder将其返回到旧位置。 通常将它与UITextField一起使用,以显示仅当UITextField是first responder时才被调用的键盘。
iOS响应链和UIEvent
响应程序链还涉及触摸屏幕,动作,点击。 当系统检测到某种事件(触摸,动作,远程控制,按下)时,将在UIEvent创建一个UIEvent ,并使用UIApplication.shared.sendEvent()方法将其发送到UIWindow 。 收到事件后, UIWindow使用hitTest:withEvent确定此事件所属的UIResponder并将其分配给第first responder 。 接下来是上面描述的UIResponder的链接列表的工作。
要使用系统UIEvent , UIResponder (UIViewController, UIView, UIApplication)子类UIResponder (UIViewController, UIView, UIApplication)可以覆盖以下方法:
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) open func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?) open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?) open func remoteControlReceived(with event: UIEvent?)
尽管存在可以手动继承和调用sendEvent功能,但UIResponder并不是为此目的而设计的。 这first responeder自定义事件的操作造成很多问题,这可能导致由随机的first responeder响应者(可以响应您的事件)引起的难以理解的动作。
为什么有用,在哪里使用
尽管iOS Responder Chain由UIKit完全控制,但仍可用于解决委派/通信问题。 UIResponder操作类似于一次性NotificationCenter.default.post 。
让我们举个例子,我们有一个UIViewController ,它位于UINavigationController堆栈的深处,我们需要告诉它在另一个屏幕上单击按钮时发生了什么。 您可以使用delagate模式或NotificationCenter.default.post ,但是一个相当简单的选项是使用iOS Responder Chain 。
button.addTarget(nil, action: #selector(RootVC.doSomething), for: .touchUpInside)
按下时,将调用UIViewController的方法。 #selector可以采用以下参数:
@objc func doSomething() @objc func doSomething(sender: Any?) @objc func doSomething(sender: Any?, event: UIEvent?)
sender是发送事件的对象-UIButton,UITextField等。
其他学习资源[eng]:
对UIEvent,UIResponder和几个高级示例的很好描述(模式协调器)
阅读有关ios响应程序链的更多信息
实践中的响应者链示例
iOS响应者链上的离线
UIResponder的离坞