[翻譯] 深入解析QML引擎, 第2部分: 綁定(Bindings)

原文 ?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,但我們知道還存在更多的綁定類型,比如編譯后綁定。它們神秘的面紗即將被揭開,敬請關注!

如果有什么疑問或者對QML應用和研究感興趣的朋友,歡迎加入我們進行討論(QQ群:280689979)。如需轉載,無須我們授權,但需要注明原文鏈接(該文的鏈接),及原作者,謝謝!

上一篇:QML文件加載? ? ?下一篇 綁定類型

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容