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

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

5 种使用Python自动化处理PDF的实用方法介绍

《5种使用Python自动化处理PDF的实用方法介绍》自动化处理PDF文件已成为减少重复工作、提升工作效率的重要手段,本文将介绍五种实用方法,从内置工具到专业库,帮助你在Python中实现PDF任务... 目录使用内置库(os、subprocess)调用外部工具使用 PyPDF2 进行基本 PDF 操作使用

nodejs打包作为公共包使用的完整流程

《nodejs打包作为公共包使用的完整流程》在Node.js项目中,打包和部署是发布应用的关键步骤,:本文主要介绍nodejs打包作为公共包使用的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言一、前置准备二、创建与编码三、一键构建四、本地“白嫖”测试(可选)五、发布公共包六、常见踩坑提醒

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

JAVA实现Token自动续期机制的示例代码

《JAVA实现Token自动续期机制的示例代码》本文主要介绍了JAVA实现Token自动续期机制的示例代码,通过动态调整会话生命周期平衡安全性与用户体验,解决固定有效期Token带来的风险与不便,感兴... 目录1. 固定有效期Token的内在局限性2. 自动续期机制:兼顾安全与体验的解决方案3. 总结PS