知识 | 将QML编译为C++: 解绑依赖关系发表时间:2022-09-20 16:16 本文翻译自Compiling QML to C++: Untangling dependencies(原文发布于2022年6月13日) 原文作者:Qt公司高级软件工程师Ulf Hermann 校审:Josh Zheng 本文是关于如何优化QML应用程序以充分利用qmlsc的系列博文的第五篇。在第一篇中,我们搭建了运行环境。为了便于理解后续文章,建议您先阅读第一篇。在之前的几篇博文中,我们已经解决了 多个 其他问题,通过本篇文章,我们将学习如何理清QML文档之间的循环依赖关系。 我们再来重新回顾一下 CategoryLabel.qml,可以看到它的“draggerParent”属性存在上一篇文章中描述的duck类型问题。当尝试编译“contentBottom”上的绑定值时,问题就会出现:
幸运的是,这里的解决方案看起来更简单。我们可以在TimelineLabels.qml初始化draggerParent属性时看出,它属于TimelineLabels类型: 现在我们虽然可以将属性指定为 TimelineLabels类型,但这样做会在运行时触发错误。TimelineLabels将CategoryLabel实例化, 也因而依赖CategoryLabel。由于QML引擎的限制,我们无法创建循环依赖关系。所以在CategoryLabel.qml中使用 TimelineLabels类型是行不通的。然而,我们需要的属性实际上又来自TimelineLabels的基本类型Flickable。在不创建循环依赖关系的情况下,我们可以这样写:
但这并没有起到什么作用:
QQuickFlickable以及它的非FINAL属性不在我们的控制范围内(尽管不是FINAL这件事本身就是QtQuick中的一个bug)。让我们回想一下,当我们发现循环依赖关系时,可能就已经开始怀疑这一点了。QML组件之间的循环依赖通常意味着组件耦合过紧。与其将 Flickable传递给每个CategoryLabel,并将每个CategoryLabel与Flickable的各项参数绑定,我们可能只需要让 CategoryLabel 获取它需要的任何信息来定位它自己。然后它也可以在一个具有不同父元素的上下文中实例化。 我们需要实现的是让 CategoryLabel 在父元素的当前可见区域中能够被拖动,同时将其定位在“内容”区域中。因此我们需要传递一个 contentY和一个visibleHeight。在此基础上,我们可以将draggerParent保留为Item,以便在选择父元素时保持灵活性,同时仍然允许 CategoryLabel能够拖动。我们也可以在这里添加一些默认值: property Item draggerParent property real contentY: 0 property real visibleHeight: contentHeight 实例化如下所示:
这一步有什么作用?让我们看一下在“contentBottom”上的绑定值,然后修改为:
我们可以对Qt Creator加载示例跟踪文件进行分析,就像我们在之前的博文中所做的那样。这里的绑定赋值执行了82次。如果没有优化,整个绑定赋值耗时为153微秒,执行 JavaScript就要耗费108微秒。经过优化后,这两段时间分别缩短至66微秒和34微秒。 这一优化很大程度上是因为我们减少了该绑定值所需的查找次数。在这之前我们必须用 draggerParent.contentY和 draggerParent.height,而非contentY和visibleHeight。查找draggerParent.contentY首先要经过在一定范围内查找draggerParent,然后在结果对象中再查找 contentY。由于解释器无法证明此操作不会改变draggerParent本身,因此在获取draggerParent.height时会对draggerParent进行重复查找。因此,以前要执行五次查找操作,经过优化后只需要三次。但是,由于我们初始化CategoryLabel的方式有所不同,其中的一些操作是发生在其他地方的。因此,我们需要进行更详尽的分析才能确定累积优化时间的确切数字。 您可能会尝试整个“contentBottom”的绑定值内联到它被调用的地方,但这通常并不是一个好主意。如果我们这样做,当类别扩展时,类别下的每个子标签都会进行重复计算。如前文所述,检索值所需的查找是有开销的。查找一个值(contentBottom)比查找三个值(contentY、visibleHeight、dragOffset)的开销要小得多。 兼容性 QML从早期版本开始就已经支持这类优化。这种解耦组件的方式是个不错的选择。而Qt 6可以为您在将QML编译为C++方面提供额外的便利。 |