不用但一定要懂 ---- iOS 之 响应链、传递链 与 手势识别

iOS 事件的主要由:响应连 和 传递链 构成。一般事件先通过传递链,传递下去。响应链,如果上层不能响应,那么一层一层通过响应链找到能响应的UIResponse。

  • 响应链:由最基础的view向系统传递,first view -> super view -> ... -> view controller -> window -> Application -> AppDelegate
  • 传递链:有系统向最上层view传递,Application -> window -> root view -> sub view -> ... -> first view

一、谁来响应事件 —— 传递链

只有继承了UIResponser的对象才能够接受处理事件。UIResponse是响应对象的基类,定义了处理各种事件的接口。在 UIKit 中我们使用响应者对象Responder接收和处理事件。一个响应者对象一般是 UIResponder 类的实例,它常见的子类包括 UIViewUIViewControllerUIApplication,这意味着几乎所有我们日常使用的控件都是响应者,如 UIButtonUILabel 等等。

UIResponder 及其子类中,我们是通过有关触摸UITouch的方法来处理和传递事件UIEvent,具体的方法如下:

open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

UIResponder 还可以处理 UIPress、加速计、远程控制事件,这里仅讨论触摸事件。

UITouch 内,存储了大量触摸相关的数据,当手指在屏幕上移动时,所对应的 UITouch 数据也会更新,例如:

  • 这个触摸是在哪个 window 或者哪个 view 内发生的?
  • 当前触摸点的坐标是?
  • 前一个触摸点的坐标是?
  • 当前触摸事件的状态是?
    这些都存储在 UITouch 里面。另外需要注意的是,在这四个方法的参数中,传递的是 UITouch 类型的一个集合 (而不是一个 UITouch),这对应了两根及以上手指触摸同一个视图的情况。

我们以UIView 来作为视图层级的主要组成元素,便于理解。但不止 UIView 可以响应事件,实际只要是 UIResponder 的子类,都可以响应和传递事件。

当我们触摸了屏幕。此时所拥有的信息是触摸点的坐标,但无法直接知道用户是想点哪个视图。需要一个策略来找到这个第一响应者,UIKit 为我们提供了命中测试hit-testing来确定触摸事件的响应者,这个策略具体是怎么运作的?

命中测试

图中还有一些细节需要先说明:

  • 在 检查自身可否接收事件 中,如果视图符合以下三个条件中的任一个,都会无法接收事件:
    1. view.isUserInteractionEnabled = false
    2. view.alpha <= 0.01
    3. view.isHidden = true
  • 检查坐标是否在自身内部 这个过程使用了 func point(inside point: CGPoint, with event: UIEvent?) -> Bool 方法来判断坐标是否在自身内部,该方法是可以被重写的。
  • 从后往前遍历子视图重复执行 指的是按照 FILO 的原则,将其所有子视图按照「后添加的先遍历」的规则进行命中测试。该规则保证了系统会优先测试视图层级树中最后添加的视图,如果视图之间有重叠,该视图也是同级视图中展示最完整的视图,即用户最可能想要点的那个视图。
  • 在 按顺序看看平级的兄弟视图 时,若发现已经没有未检查过的视图了,则应走向 诶?没有子视图符合要求?

我们举个例子来解释这个流程,在例子中我们从当前 UIViewController 的根视图开始执行这个流程。下图中灰色视图 A 可以看作是当前 UIViewController 的根视图,右侧表示了各个视图的层级结构,用户在屏幕上的触摸点是🌟处,并且这 5 个视图都可以正常的接收事件。⚠️并且注意,D 比 B 更晚添加到 A 上。

具体的流程如下:

  • 首先对 A 进行命中测试,显然🌟是在 A 内部的,按照流程接下来检查 A 是否有子视图。
  • 我们发现 A 有两个子视图,那我们就需要按 FILO 原则遍历子视图,先对 D 进行命中测试,后对 B 进行命中测试。
  • 我们对 D 进行命中测试,我们发现🌟不在 D 的内部,那就说明 D 及其子视图一定不是第一响应者。
  • 按顺序接下来对 B 进行命中测试,我们发现🌟在 B 的内部,按照流程接下来检查 B 是否有子视图。
  • 我们发现 B 有一个子视图 C,所以需要对 C 进行命中测试。
  • 显然🌟不在 C 的内部,这时我们得到的信息是:触摸点在 B 的内部,但不在 B 的任一子视图内。
  • 得到结论:B 是第一响应者,并且结束命中测试。
  • 整个命中测试的走向是这样的:A✅ --> D❎ --> B✅ --> C❎ >>>> B

实际上这个流程就是 UIView 的一个方法:func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?,方法最后返回的 UIView? 即第一响应者,这个方法代码还原应该是这样的:

class HitTestExampleView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
            return nil // 此处指视图无法接受事件
        }
        if self.point(inside: point, with: event) { // 判断触摸点是否在自身内部
            for subview in subviews.reversed() { // 按 FILO 遍历子视图
                let convertedPoint = subview.convert(point, from: self)
                //这句是判断触摸点是否在子视图内部,在就返回视图,不在就返回nil
                let resultView = subview.hitTest(convertedPoint, with: event) 
                if resultView != nil { return resultView }
            }
            return self // 此处指该视图的所有子视图都不符合要求,而触摸点又在该视图自身内部
        }
        return nil // 此处指触摸点是否不在该视图内部
    }
}

 

小心越界!

针对这个流程举个额外的例子,如果按下图的视图层级和触摸点来判断的话,最终获得第一响应者仍然是 B,甚至整个命中测试的走向和之前是一样的:A✅ --> D❎ --> B✅ --> C❎ >>>> B,究其原因是在 D 检查触摸点是否在自身内部时,答案是否,所以不会去对 E 进行命中测试,即使看起来我们点了 E。这个例子告诉我们,要注意可点击的子视图是否会超出父视图的范围。另若有这种情况可以重写 func point(inside point: CGPoint, with event: UIEvent?) -> Bool方法来扩大点击有效范围。对于这种处理方式,个人觉得是可以,但没必要,寻求合理的视图布局和清晰易读的代码比这个关键。

二、怎样传递事件 —— 响应链

确定响应链成员

在找到了第一响应者之后,整个响应链也随着确定下来了。所谓响应链是由响应者组成的一个链表,链表的头是第一响应者,链表的每个结点的下一个结点都是该结点的 next 属性。

其实响应链就是在命中测试中,走通的路径。用上个章节的例子,整个命中测试的走向是:A✅ --> D❎ --> B✅ --> C❎,我们把没走通的❎的去掉,以第一响应者 B 作为头,依次连接,响应链就是:B -> A。(实际上 A 后面还有控制器等,但在该例子中没有展示控制器等,所以就写到 A)

默认来说,若该结点是 UIView 类型的话,这个 next 属性是该结点的父视图。但也有几个例外:

  • 如果是 UIViewController 的根视图,则下一个响应者是 UIViewController。
  • 如果是 UIViewController
    • 如果 UIViewController 的视图是 UIWindow 的根视图,则下一个响应者是 UIWindow 对象。
    • 如果 UIViewController 是由另一个 UIViewController 呈现的,则下一个响应者是第二个 UIViewController。
  • UIWindow的下一个响应者是 UIApplication。
  • UIApplication 的下一个响应者是 app delegate。但仅当该 app delegate 是 UIResponder 的实例且不是 UIView、UIViewController 或 app 对象本身时,才是下一个响应者。

下面举个例子来说明。如下图所示,触摸点是🌟,那根据命中测试,B 就成为了第一响应者。由于 C 是 B 的父视图、A 是 C 的父视图、同时 A 是 Controller 的根视图,那么按照规则,响应链就是这样的:

视图 B -> 视图 C -> 根视图 A -> UIViewController 对象 -> UIWindow 对象 -> UIApplication 对象 -> App Delegate

图中浅灰色的箭头是指将 UIView 直接添加到 UIWindow 上情况

沿响应链传递事件

触摸事件首先将会由第一响应者响应,触发其 open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 等方法,根据触摸的方式不同(如拖动,双指),具体的方法和过程也不一样。若第一响应者在这个方法中不处理这个事件,则会传递给响应链中的下一个响应者触发该方法处理,若下一个也不处理,则以此类推传递下去。若到最后还没有人响应,则会被丢弃(比如一个误触)。 我们可以创建一个 UIView 的子类,并加入一些打印函数,来观察响应链具体的工作流程。

class TouchesExampleView: UIView {
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Touches Began on " + colorBlock)
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Touches Moved on " + colorBlock)
        super.touchesMoved(touches, with: event)
    }
  
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("Touches Ended on " + colorBlock)
        super.touchesEnded(touches, with: event)
    }
}

 

下面我们举一个例子。如下图,A B C 都是 UIView,我们将手指按照🌟的位置和箭头的方向在屏幕上移动一段距离,然后松开手。我们应该能在控制台看到下右图的输出。我们可以看到,A B C 三个视图都积极的响应了每一次事件,每次触摸的发生后,都会先触发 B 的响应方法,然后传递给 C,在传递给 A。但是这种「积极」的响应其实意味着在我们这个例子中,A B C 都不是这个触摸事件的合适接受者。他们之所以「积极」的将事件传递下去,是因为他们查看了这个事件的信息之后,认为自己并不是这个事件的合适处理者。(当然了,我们这边放的是三个 UIView,他们本身确实也不应该能处理事件)

那么如果我们把上图中的 C 换成平时使用的 UIControl 类,控制台又会怎么打印呢?如右下图所示,会发现响应链的事件传递到 C 处就停止了,也就是 A 的 touches 方法没有被触发。这意味着在响应链中,UIControl 及其子类默认来说,是不会将事件传递下去的。在代码中,可以理解为 UIView 默认会在其 touches 方法中去调用其 next 的 touches 方法,而 UIControl 默认不会去调用。这样就做到了,当某个控件接受了事件之后,事件的传递就会终止。另外,UIScrollView 也是这样的工作机制。

UIControl 接收信息的机制是 target-action 机制,和 UIGestureRecognizer 的处理方式相关但不完全相同

总结

总的来说,触摸屏幕后事件的传递可以分为以下几个步骤:

  1. 通过「命中测试」来找到「第一响应者」
  2. 由「第一响应者」来确定「响应链」
  3. 将事件沿「响应链」传递
  4. 事件被某个响应者接收,或没有响应者接收从而被丢弃

注:这些步骤都是建立在不使用 UIGestureRecognizer 的基础上的

三、当手势识别参与响应链

下图为手势识别处理的流程图

 

更详细的流程

 

从图中我们可以看到,在通过命中测试找到第一响应者之后,会将 UITouch 分发给 UIRespondertouches 系列方法(具体方法见上篇文章),同时也会分发给手势识别系统,让这两个处理系统同时工作。

首先要注意的是,上图中蓝色部分的流程并不会只执行一次,举例来说:当我们用一根手指在一个视图上缓慢滑动时,会产生一个 UITouch对象,这个 UITouch 对象会随着你手指的滑动,不断的更新自身,同时也不断地触发 touches 系列方法。一般来说,我们会得到如下类似的触发顺序:

touchesBegan     // 手指触摸屏幕
touchesMoved     // 手指在屏幕上移动
touchesMoved     // ...
...
touchesMoved     // ...
touchesMoved     // 手指在屏幕上移动
touchesEnded     // 手指离开屏幕

UITouchgestureRecognizers 属性中的存储了在寻找第一响应者的过程中收集到的手势,而在不断触发touches 系列方法的过程中,手势识别系统也在在不停的判断当前这个 UITouch 是否符合收集到的某个手势。

当手势识别成功: 被触摸的那个视图,也就是第一响应者会收到touchesCancelled的消息,并且该视图不会再收到来自该 UITouch 的 touches 事件。同时也让该 UITouch 关联的其他手势也收到touchesCancelled,并且之后不再收到此 UITouchtouches事件。这样做就实现了该识别到的手势能够独占该 UITouch。具体表现参考如下:

touchesBegan     // 手指触摸屏幕
touchesMoved     // 手指在屏幕上移动
touchesMoved     // ...
...
touchesMoved     // ...
touchesMoved     // 手指在屏幕上移动
touchesCancelled // 手势识别成功,touches 系列方法被阻断
// 现在手指并没有离开屏幕
// 但如果继续滑动🛹的话
// 并不会触发 touches 系列方法

 

当手势识别未成功: 指暂时未识别出来,不代表以后不会识别成功,不会阻断响应链。注意这里指的是未成功,并不一定是失败。在手势的内部状态中,手势大部分情况下状态是 .possible,指的是UITouch 暂时与其不匹配,但之后可能有机会识别成功。而.fail是真的识别失败,指的是以目前的触摸情况来看已经不可能是这个手势了,并且在下个runloop 会从 gestureRecognizers中移除该手势。

举个例子

下面举个简单的例子模拟一下响应链和手势的相互影响。现在用一根手指,在一个视图上触摸并滑动一段距离。下图给出了视图不带手势的情况,和带一个 UIPanGestureRecognizer 手势的情况。

从图中我们可以看到,当不带手势的情况下,手指按下去的时候,响应者的 touchBegan 方法会触发,随着手指的移动,touchMoved会不断触发,当手指结束移动并抬起来的时候,touchEnded 会触发。在这个过程中,我们接收到一直是一个不断更新的 UITouch

在该视图有添加一个 UIPanGestureRecognizer 手势的情况下,我们多了下方这一条来表示与响应链同时工作的手势识别系统,可以看到手势识别系统也是在手指按下去那一刻就开始工作的,前半段处于一直正在识别的状态。在我们拖动了很小一段距离之后(注意这时候我们的手指还没抬起), 手势识别系统确定了该UITouch所做的动作是符合 UIPanGestureRecognizer 的特点的,于是给该视图的响应链发送了touchCancelled 的信息,从而阻止这个UITouch继续触发这个视图的 touches 系列方法(同时也取消了别的相关手势的 touches系列方法,图中未体现)。在这之后,被调用的只有与手势关联的target-action 方法(也就是图中的墨绿色节点 call PanFunction)。

再进一步理解

为了图片的美观和易读,在图片中我隐去了不少细节,在此列出:

1、手势识别器的状态在图中未标出:

  • 手势在图中 recognizing 的橙色节点处和 recognized棕色节点处都处于.possible状态
  • 手势在图中绿色节点处的状态变化是.began -> [.changed] -> ended

2、手势识别器不是响应者,但也有touches 系列方法,比它所添加的视图的 touches方法更早那么一点触发

  • 从图中也可以看出,手势那条线上的每个节点都稍靠左一些
  • 手势那条线上的橙、棕、墨绿色节点处也可以看做手势识别器的 touches 方法触发

3、更详细的触发顺序应当如下图所示(在一个 UIView 上添加了 UIPanGestureRecognizer ,并单指在上面滑动一段距离的情况)

手势和响应者的 touches 方法名字是一样的,都是「began」「moved」「ended」「cancelled」。很容易和手势识别器的 state属性搞混,state属性是根据每个手势的类型(离散型/连续型)的不同,可能有 .possible、.began、.changed、.ended、.cancelled、.failed 这些状态,名字很像方法名很像但不是一回事。

我们可以通过配置手势的属性来改变它的表现,下面介绍三个常用的属性:
1、cancelsTouchesInView:该属性默认是 true。顾名思义,如果设置成false,当手势识别成功时,将不会发送touchesCancelled给目标视图,从而也不会打断视图本身方法的触发,最后的结果是手势和本身方法同时触发。有的时候我们不希望手势覆盖掉视图本身的方法,就可以更改这个属性来达到效果。

2、delaysTouchesBegan:该属性默认是 false。在上个例子中我们得知,在手指触摸屏幕之后,手势处于 .possible 状态时,视图的 touches 方法已经开始触发了,当手势识别成功之后,才会取消视图的 touches 方法。当该属性时 true 时,视图的touches 方法会被延迟到手势识别成功或者失败之后才开始。也就是说,假如设置该属性为 true ,在整个过程中识别手势又是成功的话,视图的touches 系列方法将不会被触发。

3、delaysTouchesEnded:该属性默认是 true。与上个属性类似,该属性为true时,视图的touchesEnded将会延迟大约 0.15s 触发。该属性常用于连击,比如我们需要触发一个双击手势,当我们手指离开屏幕时应当触发touchesEnded,如果这时该属性为 false,那就不会延迟视图的 touchesEnded方法,将会立马触发 ,那我们的双击就会被识别为两次单击。当该属性是 true 时,会延迟 touchesEnded 的触发,将两次单击连在一起,来正常识别这种双击手势。

UIControl 与手势识别

由于 UIControl 接收 target-action 方法的方式是在其 touches 方法中识别、接收、处理,而手势的 touches 方法一定比其所在视图的 touches 方法早触发。再根据上文的描述的触发规则,可以得到的结论是:对于自定义的 UIControl 来说,手势识别的优先级比 UIControl 自身处理事件的优先级高。

举个例子来说:当我们给一个 UIControl 添加了一个 .touchupInside 的方法,又添加了一个 UITapGestureRecognizer 之后。点击这个 UIControl,会看到与手势关联的方法触发了,并且给 UIControl 发送了 touchCancelled,导致其自身的处理时间机制被中断,从而也没能触发那个 .touchupInside 的方法。

同时这样的机制可能会导致一个问题:当我们给一个已经拥有点击手势的视图,添加一个 UIControl 作为子视图,那么我们无论怎么给该 UIControl 添加点击类型的 target-action 方法,最后的结果都是触发其父视图的手势(因为在命中测试的过程中收集到了这个手势),并且中断 UIControl 的事件处理,导致添加的 target-action 方法永远无法触发。

那其实🍎已经给我们做了一个解决方案,UIKit 对部分控件(同时也是 UIControl 的子类)做了特殊处理,当这些控件的父视图上有与该控件冲突功能的手势时,会优先触发控件自身的方法,不会触发其父视图上的那个手势。具体的控件和冲突触发方式如下图:

也举个例子来说:当我们给一个已经拥有点击手势的视图,添加一个 UIButton作为子视图,并且给按钮添加点击类型的 target-action 方法,那么当点击按钮时,按钮的 target-action 方法会触发,手势的方法会被忽略。
并且文档中也提到了,如果不想要这种情况发生,那就应当把手势添加到目标控件上(因为手势比控件更早识别到事件,也就是上文提到的给 UIControl添加了 .touchupInside 方法的例子),这样的话生效的就是手势了。

总结

总的来说,手势识别器在大多数情况下,识别屏幕触摸事件的优先级,比控件本身的方法的优先级高。
所以在开发的过程中,注意不要让手势覆盖控件本身的方法实现。同时也要理解默认情况下,手势识别在一开始实际上并不会阻止控件自身的 touches 系列方法,而是在之后的某个时机去取消。另外在 UIKit 中,也对部分情况做了特殊处理,让 UIKit 控件有机会跳过父视图的手势识别,去获得事件的控制权。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/9928.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

初谈 ChatGPT

引子 最近&#xff0c;小编发现互联网中的大 V 突然都在用 ChatGPT 做宣传&#xff1a;“ChatGPT不会淘汰你&#xff0c;能驾驭ChatGPT的人会淘汰你”、“带领一小部分人先驾驭ChatGPT”。 确实&#xff0c;ChatGPT这个新生事物&#xff0c;如今被视为蒸汽机、电脑、iPhone 般的…

EfficientNet V2

目录 1. EfficientNet V1存在的问题 2. EfficientNet V2 的亮点 3. EfficientNet V2 网络架构 1. EfficientNet V1存在的问题 针对EfficientNet V1 &#xff0c;作者提出了以下的三个缺点 当训练图像的size很大时&#xff0c;网络中传递的特征图尺寸就会很大&#xff0c;这…

(链表专题) 234. 回文链表——【Leetcode每日一题】

234. 回文链表 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true 示例 2&#xff1a; 输入&…

Vue:组件化开发

一、组件的使用 1、创建组件(结构HTML 交互JS 样式CSS) Vue.extend({该配置项和new Vue的配置项几乎相同&#xff0c;略有差别}) 区别&#xff1a;①创建vue组件的时候&#xff0c;配置项中不能使用el配置项。(但是需要使用template配置项来配置模板语句) ②配置项中的da…

黑马程序员微服务技术栈教程 - 1. SpringCloud 微服务治理

教程链接&#xff1a;https://www.bilibili.com/video/BV1LQ4y127n4 黑马的资料下载链接&#xff1a;https://pan.baidu.com/s/1zRmwSvSvoDkWh0-MynwERA&pwd1234 目录认识微服务单体架构分布式架构微服务微服务结构微服务技术对比SpringCloud总结 &#x1f380;服务拆分及远…

实时翻译器-实时自动翻译器

自动翻译器——让语言不再是障碍。 在当今全球化的背景下&#xff0c;语言已不再是跨文化交流的障碍。而自动翻译技术作为突破语言壁垒的有效手段&#xff0c;越来越受到关注和需求。我们的自动翻译器就是一个高效、准确的翻译工具&#xff0c;它能够根据用户输入的内容自动识…

【DS】河南省第十三届ICPC大学生程序设计竞赛 J-甜甜圈

明天就要省赛了&#xff0c;感觉已经寄了捏 J-甜甜圈_河南省第十三届ICPC大学生程序设计竞赛&#xff08;重现赛&#xff09; (nowcoder.com) 题意&#xff1a; 思路&#xff1a; 直接模拟复杂度太高&#xff0c;因此考虑用DS优化 我们考虑用树状数组维护 在用线段树和树状…

MYSQL Row 752 was cut by GROUP_CONCAT()

因为group_concat有个最大长度的限制&#xff0c;GROUP_CONCAT函数返回的结果大小被MySQL默认限制为1024(字节)的长度。超过最大长度就会被截断掉 解决方法&#xff1a;更改配置文件&#xff0c;修改长度。 https://blog.csdn.net/zzddada/article/details/115082236 concat…

网络的基本概念

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步 文章简介&#xff1a;主要概述IP地址、端口号、协议、协议分层、封装、分用、客户端、服务器、请求、响应、两台主机之间的网络通信流程。 文章目录 目录 文章目录…

Yolo V7详解及openvino部署

论文: https://arxiv.org/abs/2207.02696 代码: https://github.com/WongKinYiu/yolov7 Anchor Anchor是一种用于目标检测的先验框(prior box)生成方法&#xff0c;由Ren等人在2015年提出。Anchor可以在不同尺度和不同纵横比下生成多个先验框&#xff0c;并通过与真实目标框的…

java equals和==的区别

目录一、equals1.前言2.重写equals方法二、三、equals和的区别一、equals 1.前言 **当用equals来比较两个引用数据类型时默认比较的是它们的地址值&#xff0c;比如创建两个成员变量完全相同对象A和对象B两个进行比较&#xff0c;比较的是两个对象的地址值是否相等&#xff0c…

〖Python网络爬虫实战⑫〗- XPATH语法介绍

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付费…

【三十天精通Vue 3】第二天 Vue 3带来的新特性

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录引言一、 Vue 3 组件化架构1.1 Composition API1.2 Vuex 3 更新1.3…

OpenGL编程指南-freeglut安装(Windows平台)

OpenGL编程指南-freeglut安装&#xff08;Windows平台&#xff09; 1、前言 学习OpenGL编程首先需要可以跟着书中的示例代码进行学习。书中使用GLUT作为示例代码的演示&#xff0c;GLUT于1998年作者不在维护并不开源&#xff0c;freeglut是一个完美的代替方案。以后我们将会通…

23年5月高项学习笔记12 —— 干系人管理

过程&#xff1a; 1. 识别干系人&#xff1a;定期识别干系人&#xff0c;分析和记录他们的利益&#xff0c;参与度、相互依赖性、影响力和对项目的潜在的影响 输入&#xff1a;立项管理文件、沟通管理计划、干系人参与计划、需求文件、变更日志、问题日志、协议&#xff08;协…

MySQL事物(基础篇)

MySQL事务事物的基本概念事物的ACID属性事务的使用事务隔离级别MVCC&ReadViewMySQL是否还存在幻读事物的基本概念 Transaction作为关系型数据库的核心组成&#xff0c;在数据安全方面有着非常重要的作用&#xff0c;本文会一步步解析事务的核心特性&#xff0c;以获得对事…

STM32CubeMx+HAL库实现USB CDC+MSC复合设备

之前的文章中介绍过STM32的USB应用&#xff0c;包括虚拟串口&#xff08;CDC&#xff09;和大容量存储设备&#xff08;MSC&#xff09;。今天来介绍USB实现CDC和MSC复合设备的方法。 硬件&#xff1a;STM32F407VET6 软件&#xff1a;STM32CubeMx v6.5F4库v1.27.1 编译环境&a…

自动驾驶概述

自动驾驶是指利用计算机视觉、机器学习、传感器等技术&#xff0c;使汽车或其他交通工具能够在没有人类干预的情况下&#xff0c;完成自主导航和行驶任务。自动驾驶技术可以提高交通安全、减少交通拥堵、提高车辆利用率等&#xff0c;并对未来的城市交通和交通工具设计产生深远…

采购招投标系统-高效管控招采流程-降低采购成本

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

【SpringBoot技术专题】「实战指南」从实战开发角度去分析操作RestTemplate的应用及使用技巧

前提介绍 当你的应用程序需要访问远程接口时&#xff0c;很容易被不同的浏览器和API调用协议弄晕。幸运的是&#xff0c;Spring框架已为我们提供了一个简单而功能强大的RestTemplate工具&#xff0c;它可以轻松地处理这些基础任务并提供一个简单的方式来访问各种API。 RestTe…