You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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的点击事件从手指触摸到最终触发方法,会经过三个独立的系统校验阶段,每个阶段都会触发一次完整的响应链遍历:
    1. 手指按下(touch down)阶段:系统校验是否存在有效响应者,决定是否展示按钮高亮效果
    2. 点击手势识别完成阶段:系统再次校验响应者有效性,决定是否触发.touchUpInside对应事件
    3. 正式投递action消息前:系统做最后一次校验,确认响应者仍可处理该方法
  • 每次遍历响应链走到ViewController节点时,都会调用ViewController的canPerformAction方法,由于ViewController确实实现了onButtonTap方法,所以三次校验都会打印onButtonTap = true的日志。
  • 日志中第一条_prepareForTapGesture: = false是UIButton内部私有手势方法的校验,当前控制器没有实现该方法,返回false属于正常流程。

三次校验全部通过后,系统会将onButtonTap消息发送给响应链上找到的第一个有效响应者(即当前ViewController),最终打印ACTION日志。


内容的提问来源于stack exchange,提问作者Nikita Ermolenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 12:57:14