
第一个例子和第二个例子有什么区别?
目标负责人是什么?
在哪种情况下,单击按钮时会调用该方法?
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的离坞