philm-iOS-wiki
  • 介绍
  • 网络层
    • 说明
  • UI
    • 说明
    • 在ios7以前使用ColorSpace的坑
    • UITableView偏移异常问题
    • drawRect时单独设置文字阴影无效
    • Xcode9下相册访问权限问题
    • 避免同时点击多个Button
    • scroll上的button延迟响应问题
    • uibutton触发边界事件
    • ios 11 上tableview 改动
    • YYImage 显示指定区域的图片
  • 数据持久化
    • 说明
  • 其它
    • 取消延迟执行之坑
    • NSString 转换 float 的精度问题
  • 每周阅读
    • 目录
    • 深入思考NSNotification
    • gitBook使用小助手
    • iOS App签名的原理
    • 响应链
    • iOS10跳转系统到设置页
    • SDWebImage下载高清图内存问题
    • iOS圆角避免离屏渲染
    • 常用的延时调用
    • iOS 神经网络
    • SDWebImage缓存策略
    • 3Dtouch
    • 为什么 Objective-C 对象存储在堆上而不是栈上
    • 深入浅出理解视频编码H264结构
    • CATextLayer学习
    • cocoaPods
    • 任意网站支持RSS
    • Metal简介
    • 动态更改icon
    • CAReplicatorLayer
    • 增加点击间隔
    • 勒索病毒当道的时代
    • iOS常用宏定义
    • Metal实现YUV转RGB渲染视频
    • 获取当前下载的app及下载进度
    • OpenGL ES 三种类型修饰 uniform attribute varying
    • 技术部门引入OKR
    • 基于runloop的线程保活、销毁与通信
    • 深入理解哈希表
    • TOLL-FREE BRIDGING 和 UNMANAGED
    • 开发者能拿到的标识符
    • Swift自定义LOG
    • 系统通知整理
    • iOS 中的 imageIO 与 image 解码
    • CGImageRef基本介绍及方法说明
    • Swift 3.0 语法
    • webview加载部分网页
    • 在CAAnimation中暂停动画
    • 把代码迁移到协调器上
    • ios11API更新整理
    • 非越狱iOS设备的远程控制实现原理
    • 关于本地化
    • swift命名空间
    • CoreML与coremltools体验
    • 力学动画
    • Swift 4官方文档中文版: The Basic(上)
    • swift 中的KVO用法
    • GPUImage的图像形变设计(简单形变部分)
    • iOS响应式架构
    • 移动端图片上传旋转、压缩的解决方案
    • AVFoundation使用指南AVAssert使用
    • 过渡动画
    • 谈谈 MVX 中的 Model
    • AVFoundation编程-AVPlayer使用
    • GPUImage的图像形变设计(复杂形变部分)
    • What's New in LLVM 9
    • ios的事件机制
    • GPUImage源码解读(一)
    • GPUImage源码解读(二)
    • iOS 启动优化
    • 模块化 Swift 中的状态
    • swift中的let和var背后的编程模式
    • Swift Runtime动态性分析
    • RAC下的响应式编程
    • GPUImage源码解读(三)
    • 如何准确判断webView是否加载完成
    • NSObject的+load和+initialize详解
    • ios8以后设置启动图
    • GPUImage源码解读(四)
    • Swift自动闭包
    • IOS11新特性
    • GPUImage源码解读(五)
    • 理解 OC 内部的消息调用、消息转发、类和对象
    • 修饰符
    • IOS 切面统计事件解耦
    • GPUImage源码解读(六)
    • CoreImage介绍
    • 影响Core Animation性能的原因
    • Instruments中的动画工具选项介绍
    • GPUImage源码解读(七)
    • Xcode 7新的特性Lightweight Generics 轻量级泛型与__kindof修饰符
    • GPUImage源码解读(八)
    • Core Image之自定 Filter
    • iOS通用链接
    • 谈nonatomic非线程安全问题
    • 深拷贝与浅拷贝
    • CIKernel 介绍
    • iOS11适配
    • GPUImage源码解读(九)
    • CVPixelBufferCreate使用的坑
    • ios一窥并发底层
    • ARKit进阶:物理世界
    • ARKit的工作原理及流程介绍
    • UI线程卡顿监控
    • FBKVOController使用
    • GPUImage源码解读(十)
    • WKWebView在ios11崩溃问题解决方法
    • 微信iOS SQLite源码优化实践
    • HEIF 和 HEVC 研究
    • 谈谈 iOS 中图片的解压缩
    • 提升 iOS 开发效率! Xcode 9 内置模拟器的9个技巧
    • ObjC和JavaScript的交互,在恰当的时机注入对象
    • iOS数据保护
    • iOS11中网络层的一些变化(Session707&709脱水版)
    • GPUImage源码解读(十一)
    • 一种避免 iOS 内存碎片的方法
    • pods的原理
    • GPUImage源码解读(十二)
    • GPUImage源码解读(十三)
    • iOS 11 Layout的新特性
    • iOS应用瘦身方法思路整理
    • GPUImage源码解读(十四)
    • CAEmitterLayer属性介绍
    • 浅析移动蜂窝网络的特点及其省电方案
    • 如何在 table view 中添加 3D Touch Peek & Pop 功能
    • iOS中锁的介绍与使用
    • NSLog效率低下的原因及尝试lldb断点打印Log
    • GPUImage源码解读(十五)
    • GPUImage源码解读(十六)
    • CADisplayLink
    • GPUImage源码解读(十七)
    • CADisplayLink
    • 老生常谈category增加属性的几种操作
    • 30行代码演示dispatch_once死锁
    • GPUImage源码解读(十八)
    • YYImage设计思路
    • GPUImage源码解读(十九)
    • 深入理解Tagged Pointer
    • iOS 11:WKWebView内容过滤规则详解
    • Swift语法对编译速度的影响
    • GPUImage源码解读(二十)
    • GPUImage源码解读(二十一)
    • iOS App间常用的五种通信方式
    • YYCache深入学习
    • 冲顶大会插件
    • iOS高性能图片架构与设计
    • YUV颜色编码解析
    • iOS传感器:App前后台切换后,获取敏感信息使用touch ID进行校验
    • GPUImage源码解读(二十二)
    • GPUImage源码解读(二十三)
    • 从零开始的机器学习 - Machine Learning(一)
    • 从零开始的机器学习 - Machine Learning(二)
    • GPUImage源码解读(二十四)
    • Objective-C消息转发机制
    • iOS 程序 main 函数之前发生了什么
    • MMKV--基于 mmap 的 iOS 高性能通用 key-value 组件
    • Objective-C 消息发送与转发机制原理
    • 谈Objective-C block的实现
    • GPUImage源码解读(二十五)
    • podfile语法
    • 轻量级低风险 iOS 热更新方案
    • 使用objection来模块化开发iOS项目
    • swift 中delegate的使用注意
    • 使用appledoc自动生成api文档
    • UITextChecker的使用
    • ARKit 如何给SCNNode贴Gif图片
    • Unity与iOS平台交互和原生插件开发
    • SceneKit编程珠玑
Powered by GitBook
On this page
  • 引言
  • 事件(事件)
  • 主要事件循环
  • 响应者对象和响应者链
  • 命中测试视图
  • 命中测试的应用
  • 事件传递
  1. 每周阅读

ios的事件机制

PreviousWhat's New in LLVM 9NextGPUImage源码解读(一)

Last updated 7 years ago

引言

本文讲解主要讲解iOS事件机制相关的知识,包括事件事件,应用程序的结构,响应者对象,主事件循环,响应者链以及相关的使用。

事件(事件)

iOS版的事件主要有三种事件,

  • 触摸事件(触摸事件):iOS中最主要的就是触摸事件了,比如手指点击,手指移动以及手指离开屏幕等等。手指触摸屏幕的时候,iOS系统会生成UITouch对象来表示这次触摸,UITouch对象保存了触摸的一些信息,比如所接触的视图,触摸点的坐标,时间戳,以及触摸的时间(UITouchPhase)。有了这些触摸对象后,操作系统会生成事件对象(UIEvent),事件对象保存了之前的一系列UITouch对象,然后操作系统把这个事件对象分发到能处理这个事件的视图中(或者其他的对象,比如视图控制器,窗口,只要是应答器的子类都能响应事件),如下图所示

所有继承自UIResponder的是可以响应事件的,它有四个对应的方法

1、touchesBegan:withEvent:方法手指开始触摸的时候 2、touchesMoved:withEvent:方法手指移动的时候 3、touchesEnded:withEvent:方法手指离开的时候 4、触摸事件:与事件:外部事件到来的时候,比如电话打进来。这些方法的第一个参数是一个集合对象(集),里面保存了一系列的触摸对象,第二个参数UIEvent对象。

  • 运动事件(运动事件):比如摇一摇,系统检测到这些事件,然后发送给一个应用程序,然后由应用程序发送给第一响应者(第一响应者)。

  • 远程事件(远程事件):不好很懂,比如耳机按键控制。

    应用程序的结构

    在iOS中,视图的组织是以树状的形式组织起来的,如下图所示,一个简单的iOS的应用程序就大概有这样的结构,

整个应用程序由UIApplication的表示,应用程序管理了一个窗口对象,窗口里面管理着许多控制器,控制器又有许多的视图构成。

每一个iOS应用程序启动后都是一个独立的进程,UIApplication对象就代表一个应用程序,在iOS中,每一个应用程序有但只有一个单利形式存在的UIApplication对象,可以通[UIApplication sharedApplication]获取这个对象。应用程序的主要作用是管理应用程序的状态,以及接受和分发事件。比如下面这张图表示的,
UIApplication的可以接受用户输入事件或者系统事件,并把这些事件分发给合适的响应者对象处理,同时UIApplication的管理着系统的状态,比如应用程序启动完毕,应用程序进入后台,应用程序从后台起来或者接受通知,等等。所有这些状态的改变都由应用程序来管理,在实际的开发中,这些状态的改变会发给申请的委托,通过委托来做响应的处理。

主要事件循环

跟其他平台的应用程序一样,iOS应用程序启动之后也有一个消息循环系统,在iOS中被成为主事件循环,内部是一个运行循环,如下图所示

响应者对象和响应者链

响应者对象就是能响应事件的对象,在的UIKit中,有一个UIResponder类,这个类是所有能响应事件的类的父类,比如说视图和视图控制器,甚至是UIApplication的。

由上文的应用程序的结构可以看出iOS中所有的视图都是按照树形来组织的,每一个视图都有自己的超视图,包括viewcontroller的顶视图(也就是控制器的self.view),当一个视图被加载到superview上的时候,它的nextResponder属性就会指向它的superview,并且控制器的topmost view的nextResponder会指向所在的viewcontroller,controller的nextResponder指向窗口,Window的nextResponder指向UIApplication,这样,整个app就通过nextResponder串成一条链,就是所谓的响应链。响应链是一条虚拟的链,就是每个响应的nextResponder属性链接起来的。

命中测试视图

命中测试查看就是具体响应事件的view.window会根据不同的事件寻找这个视图,对于触摸事件,窗口最先传递给事件发生的那个视图,对于运动事件和远程事件,窗口最先把事件传递个人第一响应者事件和远程事件先不讲话,下面主要讲寻找触摸事件响应者。

寻找具体响应的过程被成为HIT-Testing.Hit-测试就相当于一个探测器,通过这个探测器可以找到手指是否点击在某个视图上面,在代码里面,这个命中测试就是一个检测方法,这个方法存在于的UIView中。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

每个手指触摸屏幕,UIApplication接受触摸的事件后,就会去调UIWindow的hitTest:withEvent:方法,看看当前的触摸的点是否在窗口内,如果在,则继续递归的调用subview的hitTest: (CGPoint)point withEvent:方法,直到找到最后视图。通过下面这张图看看它是怎么工作的。

上图的关系是,UIWindow有一个MainView,MainView有三个子视图:视图A,视图B,视图C,三个子视图分别有两个子视图,视图A,B,C的层级关系是视图A在最下面,视图B中间,视图C最上,并且视图A和视图B有一部分重叠。此时,如果手指点击在视图A和视图B的重叠部分,按照hit-test的方式,顺序如下图所示

这里的查找顺序是从窗口开始的,一开始,向根结点UIWindow发送hitTest:withEvent:消息,这个方法返回的视图就是Hit-Testing View,也就是最后能响应事件的视图当向窗口发送hitTest :withEvent:的时候,hitTest:withEvent:会检测当前的点击是否在窗口的显示范围内,如果在,则递归的调用subview的hitTest:withEvent:方法,这里调用的顺序是按照subview显示的层级来的,越在上面的子视图越早调用hitTest:withEvent :,如果点击的点不在当前的subview的返回内,则它的子视图也不会在遍历了。比如说遍历到视图C,发现点击的点不在视图C的返回内,那么就不在遍历视图C的子视图,直接遍历视图B,发现在视图B的显示返回内,则继续调用视图B的子视图的hitTest:withEvent:进行遍历,直到找到视图B.1 ,说明视图B.1就是命中测试视图直接返回到根结点,这时视图A也不会在遍历了(这也说明了被覆盖的vi EW为什么默认情况下不能响应用户的操作)下面整个过程的流程图。:

注意这里判断的时候还会检测视图的一些属性,比如userInteractionEnabled,隐藏,α,这些属性都会影响到视图是否能响应事件,如果这些不响应直接返回零(注意阿尔法的值是0.01)。同时还调了一个pointInside:withEvent:方法方法,这个也是视图的方法,用来检测一个点是否在视图的帧内,代码大概如下所示:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01 ) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subView convertPoint:point fromView:self];
            UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return  hitTestView;
            }
        }
        return self;
    }
    return nil;
}

命中测试的应用

<<<<<<< HEAD

  • 扩大按钮的点击区域:

    在实际的开发过程中,可能会出现这样的需求,一个按钮设计的很小,此时需要一个更大的点击区域,有一种方法就是加一个透明的更大的按钮来响应点击事件,这样做可以实现,但是多了一个按钮对象还有另外一种方法就是通过重写则hitTest:withEvent:,在方法里面判断如果点在按钮的帧之外的某个返回内,也返回按钮自己,这样就可以实现增大点击区域的效果,(也可以使点击区域变成一个圆形)。代码如下

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
=======

* 扩大按钮的点击区域:  
  在实际的开发过程中,可能会出现这样的需求,一个按钮设计的很小,此时需要一个更大的点击区域,有一种方法就是加一个透明的更大的按钮来响应点击事件,这样做可以实现,但是多了一个按钮对象还有另外一种方法就是通过重写则hitTest:withEvent:,在方法里面判断如果点在按钮的帧之外的某个返回内,也返回按钮自己,这样就可以实现增大点击区域的效果,(也可以使点击区域变成一个圆形)。代码如下

-(UIView )hitTest:(CGPoint)point withEvent:(UIEvent )event { if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {

6e21c461993c76bc026db05a16c36fa490c507e7 return nil; }

CGRect touchRect = CGRectInset(self.bounds, -80, -80);
if (CGRectContainsPoint(touchRect, point)) {
    for (UIView *subView in [self.subviews reverseObjectEnumerator]) {
        CGPoint convertedPoint = [subView convertPoint:point fromView:self];
        UIView *hitTestView = [subView hitTest:convertedPoint withEvent:event];
        if (hitTestView) {
            return hitTestView;
        }
    }
    return self;
}
return nil;

<<<<<<< HEAD }

=======
  }

6e21c461993c76bc026db05a16c36fa490c507e7

  • 将事件传递给兄弟视图:

    比如上图,如果视图A想要响应事件而不是视图B,不管是否重叠,在默认情况下,在重叠部分,视图A是不能响应事件的,除非让视图B的uerInteractionEnabled设为NO。如果不把userInteractionEnabled设为NO的话可以重写乙的则hitTest:withEvent:,代码如下

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hittestView = [super hitTest:point withEvent:event];
    if (hittestView == self) {
        return nil;
    }
    return hittestView;
}

事件传递

有了响应链,并且找到了第一个响应者,下面就把事件发送到这个响应者了,首先UIApplication的通过发送的SendEvent消息,把事件发送给窗口,接着窗口也发送的SendEvent消息,把事件发送给第一响应者,这个过程可以从调用堆栈中看出

当点击按钮的时候(假设第一响应者是个按钮),UIApplication的会发送开始点击和结束点击事件,分别会调到按钮的的touchesBegan和touchesEnded方法,这样就是实现了事件的传递。而事件的响应,可以看第二个图,在touchesEnded里面通过调用的UIApplication的sendAction:为:从:forEvent:。来实现如果这个按钮不响应事件,那么就会根据响应者链,把事件传给按钮的nextResponder来响应这样一级一级往上传递直到UIApplication的对象,如果应用对象还是不能响应这个事件,那么就直接抛弃这个事件。

在主事件循环里面运行着一个运行循环,应用程序不断的获取事件,并且把事件分发给能处理这个事件的对象,处理完成之后,根据处理结果更新应用程序的状态.app不断的重复这样的循环直到关闭应用程序上图里面核心对象就是能够响应事件的对象,比如说窗口,图等等。