原文  QML Engine Internals, Part 2: Bindings

译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来。翻译并不是完全直译,有不足之处,请指正,谢谢!

———————————————————————————————————————————

上一篇:QML文件加载     下一篇 绑定类型

该博文是深入解析QML引擎系列博文中的第二篇。在上一篇博文中,我们已经为大家揭示了QML引擎是如何加载QML文件的。简明扼要地回顾一下:解析QML文件,并为文件中的所有元素创建C ++对象。例如,QML文件中包含一个Text(文本)元素,QML引擎就会创建一个C ++  QQuickText类的实例。

QML引擎主要用于处理QML文件的加载,加载之后的运行时阶段就不再那么需要它了。运行时的事件处理和绘制等都是由它生成的C++类来完成的。例如,TextInput(文本输入)元素的输入事件是由QQuickTextInput::keyPressEven()处理,绘制则由QQuickTextInput::updatePaintNode()实现,完全不需要QML引擎参与。

但是在运行时,QML引擎依然会涉及到两个重要的东西:信号处理器和属性绑定更新(译者注:属性绑定更新,其实就是计算属性右边表达式的值,后续有详细的讲解,这个地方不用担心不明白。)。比如MouseArea的一个onClicked处理器就是信号处理器(译者注:MouseArea是第一篇博文的例子中的一个元素,从第一篇分析的内容,我们了解onClicked这种这样的信号处理器也被看作是属性值,和普通的属性没啥差别)。我们将在这篇文章中深入分析绑定(bindings)。在此之前请先看下面这个例子:

 
图1 QML文件例子

在这个例子中,包含了给属性赋值的两种方式:

1. 简单的赋一个值,比如给QQuickRectangle的width属性赋值300。对应的VME指令是STORE_DOUBL。它会在组件创建后执行,只是简单的调用函数QMetaObject::metacall(QMetaObject::WriteProperty,…),  该函数最终执行QQuickRectangle:setWidth()来设置width属性的值。在初始化之后,QML引擎再也不会修改width属性的值了。

2. 赋一个绑定(binding),比如给text属性赋一个绑定 "Window Area:" +(parent.width* parent.height),给anchors.centerIn属性赋一个绑定 parent。绑定的神奇之处在于,当Rectangle的height和width属性改变时,会自动更新到text属性。这是如何实现的呢?其实也没那么神奇,接下来我们将为你揭晓它的运作机制。(译者注:原作者说的binding到底是什么,下面马上就会揭晓,不用担心。)

创建绑定

通过设置QML_COMPILER_DUMP=1来输出VME指令,我们可以看到例子中的两个绑定都是由指令STORE_COMPILED_BINDING创建的:

 
图2 创建绑定的VME指令

编译后绑定是一种优化的绑定,我们还是先研究一下普通绑定,它是由STORE_BINDING指令创建的。查看QQmlVME::run()的代码,我们发现代码中创建了一个QQmlBinding对象,并把 "function $text() { return "Window Area:"+ (parent.width *parent.height) }" 做为它的表达式。没错,每一个绑定都是一个JavaScript函数!"function $text()" 这部分代码是由QML编译器添加的,这是因为QML的JavaScript引擎V8只支持完整的函数。这个JavaScript函数紧接着会被V8编译器编译成一个V8::Function对象。因为V8引擎有一个实时(JIT)编译器,所以它会生成本地的机器码(译者注:传统的JavaScript引擎是把JavaScript代码先编译为字节码,然后再通过解释器执行字节码,V8引擎运用JIT技术,不通过解释器执行字节码,而是直接把JavaScript代码编译成运行在CPU(x86/x64/ARM)上的机器码)。这时,V8:: Function对象并不会被执行,但是它会一直保留。

STORE_BINDING指令创建一个绑定可总结为:先创建了一个QQmlBinding对象,然后该对象借助V8引擎把传给它的JavaScript函数编译成了一个V8::Function对象。

(译者注:为了更容易理解后续的内容,在这里约定“绑定”即JavaScript函数,“绑定对象”即QQmlBinding对象,计算绑定的值即表示运行JavaScript函数求值,或者执行V8::Function代码求值)

运行绑定

在某些时候,绑定需要被运行,这意味着让V8引擎对绑定求值并将结果赋值给对应的属性。这些都是在创建阶段的最后阶段完成的。

QQmlVME::complete()会调用每个绑定对象的update()函数,在我们的例子中就是QQmlBinding:: update()函数。update()只是简单的执行v8:Function对象并将返回值赋给目标属性,这在我们的例子中就是Rectangle的text属性。

但是V8引擎是怎么知道parent.width和parent.height的值的呢?说实在的,它究竟是怎么知道parent对象的?答案就是:它不知道。V8引擎没有任何线索知道到底存在哪些对象,类名是什么,也不知道这些对象的属性是什么。当V8引擎遇到一个未知类或未知属性时,它会询问QML引擎中的一个对象包裹器(Object Wrapper),这个对象包裹器会找到正确的类或属性,并把他们返回给V8引擎。下面我们通过堆栈信息来看一看QQuickItem的width属性是如何被访问的:

 
图3 属性访问函数调用堆栈

从上面的堆栈信息来看,我们发现qv8qobjectwrapper.cpp中的包裹类最终调用函数QObject::qt_metacall(QMetaObject::ReadProperty,…) 来获取属性值。首先包裹类被V8代码调用,然后V8代码又被V8::Function对象对应的机器码调用。由于机器码没有堆栈帧(stack frames),因此GDB(调试工具)没法显示在??之后的堆栈信息。在上面的堆栈信息中我做了一点点假,其实它是由两个独立的堆栈信息拼起来的,细心的读者会发现,堆栈帧的序号并是不连续的。

由上可知,V8引擎会使用一个对象包裹类来获取属性值。同理,它会使用一个上下文包裹类来找到对象。例如,在我们的例子中,计算绑定值的过程中需要访问parent对象,就是通过这种方式来找到parent的。

综上所述:通过运行编译后的V8::Function代码来对绑定进行求值,再由V8引擎通过Qt里的包裹类来访问对象和属性,然后将求的值赋给目标属性。

更新绑定

好了,现在我们知道text属性是如何获得它的初始值的。但是绑定更新是如何实现的?当height和width属性改变时,QML引擎是怎么知道需要重新对绑定求值的呢?

这个问题的答案就隐藏在对象包裹类中。你应该还记得,当V8引擎需要访问一个属性时,就会调用它。这个对象包裹类不止返回属性值:它还会捕获所有被访问过的属性。从根本上讲,当一个属性被访问时,对象包裹类会调用绑定对象的捕获函数,在我们的例子中就是QQmlJavaScriptExpression::GuardCapture::captureProperty()  (QQmlBinding是QQmlJavaScriptExpression的子类)。在捕获函数内部实现中,只是简单地把绑定对象的一个槽函数连接到被捕获属性的NOTIFY信号。当NOTIFY信号被触发时,与之连接的槽函数就会被调用,并重新计算绑定的值。如果你还没有听说过NOTIFY信号,也不用担心,这很简单:当一个属性用Q_PROPERTY来声明时,在那里就可能声明了一个NOTIFY信号。只要属性发生改变,拥有该属性的对象就会触发NOTIFY信号。比如,QQuickItem的width属性的声明类似如下:

Q_PROPERTY(qrealwidth READ width WRITE setWidth NOTIFY widthChanged)

在我们这个例子中,首次运行绑定,访问width属性时,该属性的捕获函数将绑定对象中的一个槽函数连接到widthChanged()信号。在此之后,只要QQuickItem触发widthChanged()信号,对应的槽函数将被调用,并重新计算绑定的值。

这就是为什么当你的属性发生改变时,拥有并触发NOTIFY信号是非常的重要。假如你忘了这样做,绑定的值就不会被重新计算,基本上,属性绑定就无法正确的运作。另一方面,尽管属性并没有真正地改变,但你也触发了NOTIFY信号,那么绑定的值也会被毫无意义地重新计算。

综上所述:当访问属性时,对象包裹类会调用绑定对象的捕捉函数,它会将绑定对象的一个槽函数连接到该属性的NOTIFY信号,以便当属性改变时重新计算绑定的值。

结论

在这篇博文中,我们已经深入分析绑定是如何工作的。总结成一句简短的话就是:每个绑定都是一个编译过的JavaScript函数,当任何一个引用的属性改变时,它将重新被计算。

我希望你喜欢阅读这些,我确信深入研究绑定的本质是非常有趣的。

在这个系列的下一篇博文中,我们将了解不同的绑定类型。现在,我们只研究了最基本的绑定,QQmlBinding,但我们知道还存在更多的绑定类型,比如编译后绑定。它们神秘的面纱即将被揭开,敬请关注!

作者:猿基地
链接:https://www.jianshu.com/p/9b277a3ee613
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

深入解析QML引擎, 第2部分: 绑定(Bindings)的更多相关文章

  1. 深入解析QML引擎, 第3部分: 绑定类型

    原文 QML Engine Internals, Part 3: Binding Types 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/Q ...

  2. 深度解析qml引擎---(2)绑定(binding)

    强烈的希望是人生中比任何欢乐更大的兴奋剂.--尼采 上一篇文章讲了QML引擎加载qml文件的过程,大体过程是,解析qml文件,然后为文件中的每个元素创建对应的c++对象.例如,qml文件中如果使用了T ...

  3. 深入解析QML引擎, 第1部分:QML文件加载

    译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来.翻译并不是完 ...

  4. 深入解析QML引擎, 第4部分: 自定义解析器

    原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...

  5. 深度解析qml引擎---(1)Qml文件加载

                                                                        "美的事物是永恒的喜悦" --- 济慈    ...

  6. QML引擎的演进,第一部分

    原文链接:Lars Knoll – Evolution of the QML engine, part 1 QML作为一项技术对于Qt的成功变得越来越重要.它允许创建流畅的动画界面,与现今的市场预期相 ...

  7. MyBatis源码解析(十二)——binding绑定模块之MapperRegisty

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...

  8. 腾讯QQ团队开源分布式后台毫秒服务引擎全解析:引擎架构、RPC、灰度……

    腾讯QQ团队将于12月4日开源一个服务开发运营框架,叫做毫秒服务引擎(Mass Service Engine in Cluster,MSEC),它集RPC.名字发现服务.负载均衡.业务监控.灰度发布. ...

  9. 视图模板引擎——Vue【双向绑定】原理剖析

    首先我们来了解一下MVC.MVP.MVMM这三大架构模式在前端角度上的理解. MVC分别是 Model(模型).View(视图).Controller(控制器)三个模块.View(视图层)最主要完成前 ...

随机推荐

  1. UVA 12230 Crossing Rivers

    嘟嘟嘟 虽然分类是期望dp,不过好像是最水的 因为在陆地上的时间和概率是固定的,所以只用考虑过河的期望时间. 对于一条河p, l, v,p好像没什么用……不管了,首先期望时间我觉得可以这么算:期望时间 ...

  2. Maven构建项目报No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK? 问题的解决方案

    在编译SSM项目时,碰到如下问题,希望能给遇到相同问题的小伙伴们帮助 O(∩_∩)O~ Eclipse导入Maven项目后,选中父项目,执行Run AS——>Maven install后,出现如 ...

  3. Kali更新与升级

    当用户使用一段时间以后,可能对总是在没有任何变化的系统中工作感到不满,而是渴望能像在Windows系统中一样,不断对自己的Linux进行升级.另外,Linux本身就是一个开放的系统,每天都会有新的软件 ...

  4. Markdown语法初体验

    前言 由于把博客主题样式换了,所以改用Markdown语法,让代码看起来更加舒服一些. 照葫芦画瓢 这里是H1标题(===) 这里是H2标题(---) 使用一个#号 使用两个#号 使用三个#号 引用 ...

  5. Owin+ASP.NET Identity浅析系列(二)扩展用户属性

    在今天,读书有时是件“麻烦”事.它需要你付出时间,付出精力,还要付出一份心境.--仅以<Owin+ASP.NET Identity浅析系列>来祭奠那逝去的…… 上一篇博客讲了用户登录注册问 ...

  6. HDU 1069 Monkey and Banana(转换成LIS,做法很值得学习)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1069 Monkey and Banana Time Limit: 2000/1000 MS (Java ...

  7. 404 Note Found队 Alpha7

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...

  8. 【Step By Step】将Dotnet Core部署到Docker(中)

    在Docker中运行MySql MySQL 官方也提供了各种版本的MySQL Image来供用户使用,我们可以使用如下命令来创建并运行一个MySQL Image: docker run -it -p ...

  9. mysql数据库锁简介

    本篇介绍有关数据库锁相关的知识,关于数据库事务及隔离级别参见<数据库事务ACID特性及隔离级别>这篇文.   乐观锁 乐观锁最常用的实现方式是用数据版本(Version)记录机制.数据版本 ...

  10. 直接在apk中添加资源的研究

    原文 http://blog.votzone.com/2018/05/12/apk-merge.html 之前接手过一个sdk的开发工作,在开发过程中有一个很重要的点就是尽量使用代码来创建控件,资源文 ...