为何UIViewController的canPerformAction同action/sender下会多次调用
为什么target为nil时canPerformAction返回true仍会被多次调用?
问题背景
- 正在研究UIResponder底层工作原理,测试时主动给UIButton的点击事件传入
nil作为target
核心绑定代码:
button.addTarget(nil, action: #selector(onButtonTap), for: .touchUpInside)
完整测试代码
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let button = UIButton() button.setTitle("ACTION", for: .normal) button.backgroundColor = .red button.addTarget(nil, action: #selector(onButtonTap), for: .touchUpInside) view.addSubview(button) button.frame = CGRect(x: 200, y: 150, width: 170, height: 50) } @objc func onButtonTap() { print("ACTION") } override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { let can = super.canPerformAction(action, withSender: sender) print("CAN PERFORM ACTION \(action) = \(can); Sender: \(sender)") return can } }
运行输出
CAN PERFORM ACTION _prepareForTapGesture: = false; Sender: Optional(<UIButton: 0x7f966ef12fa0; frame = (200 150; 170 50); opaque = NO; layer = <CALayer: 0x6000020b0180>>) CAN PERFORM ACTION onButtonTap = true; Sender: Optional(<UIButton: 0x7f966ef12fa0; frame = (200 150; 170 50); opaque = NO; layer = <CALayer: 0x6000020b0180>>) CAN PERFORM ACTION onButtonTap = true; Sender: Optional(<UIButton: 0x7f966ef12fa0; frame = (200 150; 170 50); opaque = NO; layer = <CALayer: 0x6000020b0180>>) CAN PERFORM ACTION onButtonTap = true; Sender: Optional(<UIButton: 0x7f966ef12fa0; frame = (200 150; 170 50); opaque = NO; layer = <CALayer: 0x6000020b0180>>) ACTION
现象
日志中onButtonTap对应的canPerformAction连续三次返回true后,才最终执行onButtonTap方法。
答案
这是UIControl在target为nil时走响应链事件分发的正常表现,底层原因如下:
- 当
addTarget传入nil作为target时,系统不会在事件触发时直接给固定对象发送消息,而是在事件触发的当下,动态沿着响应链从事件源向上查找能响应action的对象。 - UIControl的点击事件从手指触摸到最终触发方法,会经过三个独立的系统校验阶段,每个阶段都会触发一次完整的响应链遍历:
- 手指按下(touch down)阶段:系统校验是否存在有效响应者,决定是否展示按钮高亮效果
- 点击手势识别完成阶段:系统再次校验响应者有效性,决定是否触发
.touchUpInside对应事件 - 正式投递action消息前:系统做最后一次校验,确认响应者仍可处理该方法
- 每次遍历响应链走到ViewController节点时,都会调用ViewController的
canPerformAction方法,由于ViewController确实实现了onButtonTap方法,所以三次校验都会打印onButtonTap = true的日志。 - 日志中第一条
_prepareForTapGesture: = false是UIButton内部私有手势方法的校验,当前控制器没有实现该方法,返回false属于正常流程。
三次校验全部通过后,系统会将onButtonTap消息发送给响应链上找到的第一个有效响应者(即当前ViewController),最终打印ACTION日志。
内容的提问来源于stack exchange,提问作者Nikita Ermolenko
相关产品推荐
相关产品推荐

