深刻解析QML引擎, 第2部分: 綁定(Bindings)

原文  QML Engine Internals, Part 2: Bindingshtml

譯者注:這個解析QML引擎的文章共4篇,分析很是透徹,在國內幾乎沒有找到相似的分析,爲了便於國內的QT/QML愛好者和工做者也能更好的學習和理解QML引擎,故將這個系列的4篇文章翻譯過來。翻譯並非徹底直譯,有不足之處,請指正,謝謝!app

———————————————————————————————————————————函數

上一篇: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)。在此以前請先看下面這個例子:ui

 
圖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究竟是什麼,下面立刻就會揭曉,不用擔憂。)orm

建立綁定

經過設置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 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索