iOS经典面试题之深入解析一个触摸事件是如何识别并执行具体逻辑的

一、硬件层 → IOKit 层(物理信号转系统事件)

  • 手指触摸屏幕:手指与电容屏接触,改变触摸点下方的电容值。
  • 硬件驱动采样:屏幕控制芯片(如基于 I2C 接口的触摸控制器)以高频率(如 120Hz 或 240Hz)采样触摸坐标、压力、手指编号等信息。
  • 中断触发 → IOKit 读取:硬件通过中断通知 CPU,IOKit 框架中的触摸设备驱动程序读取硬件寄存器,获取原始触摸数据。
  • 封装成 IOHIDEvent:驱动程序将原始数据包装成一个 IOHIDEvent 对象(HID 是 Human Interface Device 的缩写)。该对象包含:
    • 事件类型(kIOHIDEventTypeDigitizer)
    • 时间戳
    • 触摸阶段(开始/移动/结束/取消)
    • 坐标(经过屏幕校准)
    • 手指 ID、压力等
  • 发送给 SpringBoard:IOKit 通过 mach port(内核与用户空间的通信通道)将 IOHIDEvent 发送给 SpringBoard 进程,SpringBoard 是 iOS 的桌面管理程序,负责接收所有系统事件。

二、系统层(SpringBoard)→ 应用层(App)

  • SpringBoard 决定目标 App:SpringBoard 维护当前前台应用的进程 ID,它根据触摸事件的屏幕坐标,结合当前屏幕层级(如锁屏界面、通知中心、控制中心、当前 App 的窗口),判断事件应该送给哪个 App。
  • 通过 mach port 转发事件:SpringBoard 将 IOHIDEvent 再次封装,并通过目标 App 的 mach port 发送给该 App 的 UIApplication 进程。
  • App 的 RunLoop 接收事件:App 的主线程 RunLoop 注册了负责接收事件的 source1(基于 mach port 的输入源),当事件到达,RunLoop 被唤醒,调用对应的回调函数。
  • UIApplication 将系统事件转换为 UIEvent:系统回调将 IOHIDEvent 转换为 UIEvent 对象,内部包含一个或多个 UITouch 对象。每个 UITouch 记录了触摸的位置(在 UIWindow 中的坐标)、阶段、时间、力度等。
  • UIApplication 将事件放入队列:UIApplication 将生成的 UIEvent 添加到由它管理的 事件队列(NSEventQueue)中。队列遵循 FIFO(先进先出),确保事件按顺序处理。
  • 主线程 RunLoop 处理事件:RunLoop 在循环中检测到事件队列非空,取出最前面的 UIEvent,然后调用 UIApplication 的 sendEvent: 方法开始分发。

三、App事件分发与响应链

① UIWindow 接收事件
  • UIApplication 的 sendEvent: 会将事件发送给当前关键的 UIWindow 对象(通常是 keyWindow);
  • UIWindow 继承自 UIView,它的 sendEvent: 方法开始处理事件。
② Hit-Testing(寻找初始响应者)
  • Hit-Testing 这是最关键的一步,UIWindow 调用 hitTest:withEvent: 方法,递归查找触摸点所在的最终视图。
  • hitTest:withEvent: 的内部逻辑(按顺序执行):
    • 调用 pointInside:withEvent: 检查触摸点是否在当前视图的 bounds 内,若返回 NO,则直接返回 nil,不再检查子视图;
      若返回 YES,则进入下一步。
    • 从当前视图的 subviews 数组的末尾(即最上层子视图)开始倒序遍历,对每个子视图递归调用 hitTest:withEvent:,只要某个子视图返回非 nil,立即停止遍历,将该子视图返回给上层。
    • 如果所有子视图都返回 nil,则当前视图自己就是命中视图,返回 self。
  • 视图结构 A(父)→ B(子)→ C(孙),触摸点在 C 内:
    • A hitTest:\] 发现点在 A 内,遍历子视图 B;

    • B hitTest:\] 发现点在 B 内,遍历子视图 C;

    • C hitTest:\] 发现点在 C 内,C 没有子视图,返回 C;

    • B 收到 C,返回 C;
    • A 收到 C,最终 hitTest 返回 C。
  • 如果视图设置了 userInteractionEnabled = NO、hidden = YES、alpha < 0.01,则 hitTest: 直接返回 nil,视为不存在。
③ 确定最终响应者
  • UIWindow 的 sendEvent: 得到 hitTest 返回的视图,这个视图就是 第一响应者(first responder)。
④ 事件传递给第一响应者
  • UIWindow 调用第一响应者的 touchesBegan:withEvent: 方法(根据触摸阶段可能调用 touchesMoved:、touchesEnded:、touchesCancelled:)。
⑤ 响应者链(Responder Chain)的介入
  • 如果第一响应者没有实现 touchesBegan:(或其默认实现调用 super),事件会沿响应者链向上传递。
  • 响应者链的传递路径:
swift 复制代码
第一响应者(UIView)
 → 它的父视图(UIView)
 → 父视图的视图控制器(UIViewController,如果存在)
 → 根视图(UIView)
 → UIWindow
 → UIApplication
 → AppDelegate(如果实现了 UIResponder 方法)
  • 每个响应者都可以通过重写 touches* 方法并不调用 [super ...] 来拦截事件,或者调用 super 继续向上传递。
⑥ 手势识别器(UIGestureRecognizer)的介入
  • 在 UIWindow 将事件发送给第一响应者之前,UIWindow 会先将事件传递给附加在视图上的手势识别器。
  • 处理顺序:
    • UIWindow 的 sendEvent: 会先遍历视图及其祖先视图上的所有手势识别器;
    • 手势识别器以并行方式分析触摸流(UIGestureRecognizerState 变化);
    • 如果某个手势识别器识别成功(状态变为 Ended),它会接管事件。默认情况下,系统会取消原本要发给视图的触摸(发送 touchesCancelled:),然后调用手势识别器的 action 方法;
    • 可以通过 delaysTouchesBegan / delaysTouchesEnded 等属性调整手势与视图触摸的时序关系。
⑦ UIControl(控件)的特殊处理
  • 对于 UIButton、UISlider 等 UIControl 子类,它们重写了 touches* 方法,在内部实现 Target-Action 机制。当触摸事件满足一定条件(如抬起时仍在按钮内),控件会发送 action 消息(如 touchUpInside)。
  • 优先级总结(从高到低):
    • UIControl 的 Target-Action(内部完全处理,不会传递);
    • 手势识别器(比视图的 touches 方法更早识别);
    • 视图的 touchesBegan: 等响应者方法。
⑧ 事件处理完成
  • 一旦某个响应者或手势识别器消耗了事件(不再向上传递或调用 super),事件生命周期结束,RunLoop 继续处理下一个事件。

四、补充说明

① 多个手指(多点触控)
  • UIEvent 中包含一个 touches 集合,每个 UITouch 对应一根手指。
  • 通过 UIView 的 multipleTouchEnabled 属性控制是否允许多点触控。
④ 3D Touch / Haptic Touch
  • UITouch 提供 force 属性(力度值)。
  • 系统会额外发送 UIEventTypePressure 事件。
④ 主动取消(如弹窗出现)
  • 当系统弹出模态视图(如系统权限弹窗)或调用 UIApplication 的 beginIgnoringInteractionEvents,当前触摸会被取消,第一响应者收到 touchesCancelled:withEvent:。
⑤ 跨视图跟踪(UIScrollView为例)
  • UIScrollView 在 hitTest 中返回自身或子视图后,会在 touchesBegan: 中启动一个 timer,判断用户是否开始拖动。
  • 若判断为拖动,UIScrollView 会调用 [super touchesCancelled:...] 取消子视图的响应,并自己处理移动事件。
相关推荐
linweidong4 个月前
得物ios开发面试题及参考答案(下)
ios开发·appstore·runloop·自旋锁·ios版本·ios事件·app面试
RollingPin6 个月前
iOS八股文之 RunLoop
ios·多线程·卡顿·ios面试·runloop·ios保活·ios八股文
RollingPin6 个月前
iOS八股文之 内存管理
ios·内存管理·内存泄漏·ios面试·arc·runloop·引用计数
Huntto8 个月前
最小二乘法计算触摸事件速度
android·最小二乘法·触摸事件·速度估计
SchneeDuan2 年前
iOS--RunLoop原理
ios·timer·runloop
码农--xc2 年前
深入理解RunLoop
ios·线程·runloop
依旧风轻2 年前
RunLoop小白入门
ios·ios面试·runloop
千里马学框架3 年前
systemserver的inputdispatcher直接产生CANCEL事件原理分析-讨厌的android触摸面试题
android·车载系统·安卓framework开发·android系统·触摸事件·触摸cancel事件·android面试题