iOS UIGestureRecognizer与原生响应机制处理流程分析

2024-04-26 02:38

本文主要是介绍iOS UIGestureRecognizer与原生响应机制处理流程分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前在组内分享的时候,曾提到过在iOS事件响应机制中,当原生触摸事件与手势搭配时,相关方法的调用顺序。之前是将手势也理解至响应者链中,后来发现理解有误,所以在此进行一些总结。

UIGestureRecognizer与原生触摸事件均为处理用户点击事件,所以两者必然存在着紧密关联,所以在探究UIGestureRecognizer响应机制之前,我们先了解一下原生触摸事件的响应机制。

由于iOS系统的封闭性,我们在探究时可能需要做一些方法的重写、打点以及一些方法栈的打印。

以下分析是基于iOS 13.3模拟器进行的。

一、原生触摸事件响应机制

1、系统处理

点击事件是如何产生的?以及该事件是如何交给我们的App呢?

这些都属于系统处理,大致流程如下:

  1. 屏幕硬件捕获用户点击,由 IOKit封装为 IOHIDEvent对象,将该对象交由桌面 SpringBoard.app
  2. 桌面 SpringBoard.app获取当前处于前台的App,将收到的 IOHIDEvent对象发送给该App。

2、App处理

App处理过程其实网上已经做过很多次的讨论和总结了,大致过程如下:

  1. 当由 SpringBoard.app通知App时,本质上是由一个source1类型的事件唤起当前runloop,并触发 __IOHIDEventSystemClientQueueCallback回调。
  2. source1触发source0,在source0回调中,处理收到的 IOHIDEvent对象,将之封装为 UIEvent对象。
  3. App开始我们常说的寻找响应者过程,即 Hit-Testing,该过程会寻找到一系列可处理当前时间的控件。
  4. 由上一步获取到的响应者链开始依次处理事件,若事件在某个节点被处理了,那么整个过程就结束了;若事件一直未被处理,那么该事件就会被抛弃。

3. Hit-Testing

Hit-Testing目的为确定当前点击点所处视图链。

该过程主要依赖以下两个方法:

// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

我们可以搭建以下结构的视图,并重写UIView的这两个方法,通过点击E视图来了解Hit-Testing的工作原理。

在这里插入图片描述

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{NSLog(@"进入 View %@ 的 hitTest:withEvent:", self.name);UIView *view = [super hitTest:point withEvent:event];NSLog(@"离开 View %@ 的 hitTest:withEvent: %@", self.name, [view isKindOfClass:[CustomView class]] ? ((CustomView *)view).name : @"");return view;
}- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{NSLog(@"进入 View %@ 的 pointInside:withEvent:", self.name);BOOL isInside = [super pointInside:point withEvent:event];NSLog(@"离开 View %@ 的 pointInside:withEvent: %@", self.name, @(isInside));return isInside;
}

输出如下:

2020-02-19 11:22:29.909200+0800 TouchDemo[22486:1782621] 进入 View A 的 hitTest:withEvent:
2020-02-19 11:22:29.909434+0800 TouchDemo[22486:1782621] 进入 View A 的 pointInside:withEvent:
2020-02-19 11:22:29.909626+0800 TouchDemo[22486:1782621] 离开 View A 的 pointInside:withEvent: 1
2020-02-19 11:22:29.909772+0800 TouchDemo[22486:1782621] 进入 View C 的 hitTest:withEvent:
2020-02-19 11:22:29.909986+0800 TouchDemo[22486:1782621] 进入 View C 的 pointInside:withEvent:
2020-02-19 11:22:29.910132+0800 TouchDemo[22486:1782621] 离开 View C 的 pointInside:withEvent: 1
2020-02-19 11:22:29.910271+0800 TouchDemo[22486:1782621] 进入 View E 的 hitTest:withEvent:
2020-02-19 11:22:29.910418+0800 TouchDemo[22486:1782621] 进入 View E 的 pointInside:withEvent:
2020-02-19 11:22:29.910561+0800 TouchDemo[22486:1782621] 离开 View E 的 pointInside:withEvent: 1
2020-02-19 11:22:29.910715+0800 TouchDemo[22486:1782621] 离开 View E 的 hitTest:withEvent: E
2020-02-19 11:22:29.910862+0800 TouchDemo[22486:1782621] 离开 View C 的 hitTest:withEvent: E
2020-02-19 11:22:29.911060+0800 TouchDemo[22486:1782621] 离开 View A 的 hitTest:withEvent: E

有输出不难分析出,系统会通过调用hitTest:withEvent来返回一个可以响应当前事件的控件,该控件可以为自己,也可以为子控件,而pointInside:withEvent:方法则返回当前控件是否可以响应事件。

另外,在绝大多数情况下,这一套流程会触发两次,官方给出的解释如下:

Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.

即为了避免在正式处理事件之前做了对View的调整,会再进行确认,而该确认没有多余的副作用。

至此,可以处理事件的响应者链就确定了,该响应者链对应一个点击,理应应当被该点击所知,但是我们在这两个方法中打印一下UIEvent,发现其中并没有对应的UITouch,那么UITouch是在何时创建的,我们可以hook UITouch的init方法来确认一下。

在此之前,我们先获取一下在确定响应者链时的方法栈:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1* frame #0: 0x000000010feee454 TouchDemo`-[CustomView hitTest:withEvent:](self=0x00007ff79650a650, _cmd="hitTest:withEvent:", point=(x = 168, y = 783), event=0x00006000009e0000) at CustomView.m:30:53frame #1: 0x00007fff4855afbd UIKitCore`-[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87frame #2: 0x00007fff4855af0c UIKitCore`__38-[UIView(Geometry) hitTest:withEvent:]_block_invoke + 121frame #3: 0x00007fff23c3f1e7 CoreFoundation`__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 7frame #4: 0x00007fff23b848fc CoreFoundation`-[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 524frame #5: 0x00007fff4855aaa3 UIKitCore`-[UIView(Geometry) hitTest:withEvent:] + 482frame #6: 0x00007fff4855afbd UIKitCore`-[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87frame #7: 0x00007fff4855af0c UIKitCore`__38-[UIView(Geometry) hitTest:withEvent:]_block_invoke + 121frame #8: 0x00007fff23c3f1e7 CoreFoundation`__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 7frame #9: 0x00007fff23b848fc CoreFoundation`-[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 524frame #10: 0x00007fff4855aaa3 UIKitCore`-[UIView(Geometry) hitTest:withEvent:] + 482frame #11: 0x00007fff4851de63 UIKitCore`-[UIDropShadowView hitTest:withEvent:] + 242frame #12: 0x00007fff4855afbd UIKitCore`-[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87frame #13: 0x00007fff4855af0c UIKitCore`__38-[UIView(Geometry) hitTest:withEvent:]_block_invoke + 121frame #14: 0x00007fff23c3f1e7 CoreFoundation`__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 7frame #15: 0x00007fff23b848fc CoreFoundation`-[__NSArrayM enumerateObjectsWithOptions:usingBlock:] + 524frame #16: 0x00007fff4855aaa3 UIKitCore`-[UIView(Geometry) hitTest:withEvent:] + 482frame #17: 0x00007fff48533c32 UIKitCore`-[UITransitionView hitTest:withEvent:] + 44frame #18: 0x00007fff4855afbd UIKitCore`-[UIView(Geometry) _hitTest:withEvent:windowServerHitTestWindow:] + 87frame #19: 0x00007fff4855af0c UIKitCore`__38-[UIView(Geometry) hitTest:withEvent:]_block_invoke + 121frame #20: 0x00007fff23c3f1e7 CoreFoundation`__NSARRAY_IS_CALLING_OUT_TO_A_BLOCK__ + 7frame #21: 0x00007fff23b848fc CoreFoundation&#

这篇关于iOS UIGestureRecognizer与原生响应机制处理流程分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/936492

相关文章

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

MyBatis-plus处理存储json数据过程

《MyBatis-plus处理存储json数据过程》文章介绍MyBatis-Plus3.4.21处理对象与集合的差异:对象可用内置Handler配合autoResultMap,集合需自定义处理器继承F... 目录1、如果是对象2、如果需要转换的是List集合总结对象和集合分两种情况处理,目前我用的MP的版本

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

Python自动化处理PDF文档的操作完整指南

《Python自动化处理PDF文档的操作完整指南》在办公自动化中,PDF文档处理是一项常见需求,本文将介绍如何使用Python实现PDF文档的自动化处理,感兴趣的小伙伴可以跟随小编一起学习一下... 目录使用pymupdf读写PDF文件基本概念安装pymupdf提取文本内容提取图像添加水印使用pdfplum

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

Redis中哨兵机制和集群的区别及说明

《Redis中哨兵机制和集群的区别及说明》Redis哨兵通过主从复制实现高可用,适用于中小规模数据;集群采用分布式分片,支持动态扩展,适合大规模数据,哨兵管理简单但扩展性弱,集群性能更强但架构复杂,根... 目录一、架构设计与节点角色1. 哨兵机制(Sentinel)2. 集群(Cluster)二、数据分片