動態界面:DSL&佈局引擎

Jasonette 與 Tangram

很早的時候火了一陣子Jasonette,打出來的宣傳語是用json寫出純native的app(牛皮其實有點大,其實只是寫動態界面,徹底不是寫動態App)。css

前一陣子,天貓又開源了跨多個平臺的Tangram,一套通用的UI解決方案,仔細閱讀文檔咱們會發現,他們也是在用json來實現這套七巧板佈局。一套靈活的跨平臺的UI解決方案。html

Jasonette的牛皮其實有點大,不少人看到動態用Json寫出純native的app,就很激動,彷彿客戶端也能有H5那樣的能力,但其實他只是focus在解決app中的界面的問題。Tangram的定位就很精準了,是一套爲業務出發的通用跨平臺UI解決方案,把佈局渲染性能與多段一致性考慮在框架內的UI框架。這兩者有個共同點都是用json來描述界面與內容,從而用native進行呈現,json這種數據是一種自然便於下發與動態更新的數據,所以這些其實都能讓客戶端作到相似H5網頁同樣的趕腳。雖然沒有使用WebView,但他們的設計思路和網頁技術的發展歷史一模一樣,所以@響馬大叔說過"這實際上是最純正的網頁技術,雖然他是native的"。前端

順着這個話題繼續問幾個問題node

  • DSL

爲何Jasonette與Tangram都是用json?css3

  • 佈局排版

爲何Jasonette寫出來的json有些屬性看着很像css?padding & align(拿Jasonette舉例)git

  • 渲染

Jasonette調用UIKit進行渲染,H5用WebView渲染,因此Jasonette就叫native?程序員

從DSL提及

DSL 是 Domain Specific Language 的縮寫,意思就是特定領域下的語言,與DSL對應的就是通用編程語言,好比Java/C/C++這種。換個通俗易懂的說法,DSL是爲了解決某些特定場景下的任務而專門設計的語言。github

舉幾個很著名的DSL的例子web

  • 正則表達式

經過一些規定好的符號和組合規則,經過正則表達式引擎來實現字符串的匹配正則表達式

  • HTML&CSS

雖然寫的是相似XML 或者 .{} 同樣的字符規則,可是最終都會被瀏覽器內核轉變成Dom樹,從而渲染到Webview上

  • SQL

雖然是一些諸如 create select insert 這種單詞後面跟上參數,這樣的語句實現了對數據庫的增刪改查一系列程序工做

計算機領域須要用代碼解決不少專業問題,每每須要同時具有編碼能力以及專業領域的能力,爲了提升工做於生產效率,須要把一些複雜但更偏向專業領域的處理,以一種更簡單,更容易學習的語言或者規範(即DSL),抽象提供給領域專家,交給不懂編碼的領域專家編寫。

而後代碼編程能力者經過讀取解析本身定製出來的這些語言規範,來領會領域專家的意圖,最終轉化成真正的通用編程語言代碼實現,接入底層代碼框架,從而實現讓領域專家只須要學習更簡單的DSL,就能影響代碼程序的最終結果。

(雖然DSL的原始定義是爲了非編程的專業領域人才使用,但到後來直接交給程序員使用,但能大幅度提升程序員編寫效率的非通用語言,也被當作是DSL的一種)

DSL在設計上的應用

設計師會設計出不少精美的界面,最後交給程序員去用代碼編寫成漂亮的網頁或者App。每一個界面若是純用代碼編寫,都會面臨大量的代碼工做量,這裏面可能更多的是一些重複的機械性的代碼工做。諸如:某個元素設置長寬,設置居中,設置文字,設置距離上個元素XX像素,N個元素一塊兒縱向,橫向平均排列等等。對於代碼實現界面開發來講,程序員須要編寫一系列諸如:setFrame,setTitle,setColor,addSubview這樣的代碼,一邊寫這樣的代碼,一邊查閱設計師給出的各類標註。

爲了提升工做效率,若是能把一些設計師產出的長寬色值文字居中距上等設計元數據(設計的標註信息等),以一種約定的簡潔的語言規則(即DSL)輸入給程序代碼,由程序和代碼自動的分析和處理,從而生成真正的界面開發代碼setFrame,setTitle,setColor,addSubview,這樣就能夠大幅度的減小代碼量與工做量,程序員來寫這種簡潔的語法規則會更快更高效,甚至能夠把這種簡潔的語法規則教會設計師,讓設計師有能力直接寫出DSL,而後輸入給底層程序,這樣界面就天然完成。

作iOS客戶端開發的同窗有興趣能夠用文本編輯器打開如下XIB文件,你會看到咱們拖來拖去拖線出來的Xib,其實就是XML語法,而Jasonette就是Json語法,他們用XML/JSON這種通用的結構化語法來存儲這些設計數據,用一些自定義的標籤,來標記這些數據的用途,XIB/JSON通過解析後就會生成簽到字典的樹狀結構,所以代碼就能夠進行遍歷執行,從而轉變成最終的UIKit的渲染代碼。

HTML/CSS,是網頁開發廣泛使用的,他們也是一種DSL,你寫出的每個HTML的DIV以及CSS的屬性樣式,最後都不是經過.html .css文件渲染到屏幕到瀏覽器上的,都是經過瀏覽器內核最後調用OpenGL,C++代碼渲染上去的。從這個層面講,Jasonette客戶端框架用的json,native客戶端開發的XIB,與網頁瀏覽器的HTML/CSS是一回事。

XIB的XML代碼其實也通常不會交給設計師去學習和掌握,可是在XIB的XML基礎之上,製做出一個InterFaceBuilder,可讓設計師用圖形界面拖來拖去,這種UI編輯器式的設計其實都離不開DSL,都是規劃出了一種比通用代碼語言更簡潔的DSL後,再輔助開發界面編輯器生成這種簡潔DSL來實現的。

Jasonette裏的json如何工做

扯淡了這麼多,親自看看Jasonette源碼是如何執行DSL的。

{
  "$jason": {
    "head": {
      "title": "{ ˃̵̑ᴥ˂̵̑}",
      "actions": {
        "$foreground": {
          "type": "$reload"
        },
        "$pull": {
          "type": "$reload"
        }
      }
    },
    "body": {
      "header": {
        "style": {
          "background": "#ffffff"
        }
      },
      "style": {
        "background": "#ffffff",
        "border": "none"
      },
      "sections": [
        {
          "items": [
            {
              "type": "vertical",
              "style": {
                "padding": "30",
                "spacing": "20",
                "align": "center"
              },
              "components": [
                {
                  "type": "label",
                  "text": "It's ALIVE!",
                  "style": {
                    "align": "center",
                    "font": "Courier-Bold",
                    "size": "18"
                  }
                },
                {
                  ......省略
                }
              ]
            },{
                ......省略
            },
            {
              "type": "label",
              "style": {
                "align": "right",
                "padding": "10",
                "color": "#000000",
                "font": "HelveticaNeue",
                "size": "12"
              },
              "text": "Watch the tutorial video",
              "href": {
                "url": "https://www.youtube.com/watch?v=hfevBAAfCMQ",
                "view": "Web"
              }
            }
          ]
        }
      ]
    }
  }
}複製代碼

這是demo裏的頁面json代碼,你會看到不少很像網頁開發的東西,head,body,padding,align等等,是否是以爲和CSS很像

這個demo寫的json文件其實就是一個helloworld界面,裏面有一些按鈕,點擊能夠跳轉,還有一些圖片,我先簡單介紹一下Jasonette DemoApp的啓動流程

  • Application didFinishLaunchingWithOptions

程序初始化,觸發[[Jason client] start:nil],初始化Jason,在這個start裏面,會建立JasonViewController,而且給這個VC設置rootUrl,設置這個VC做爲Window的Key,從而進行App展示

  • VC viewWillAppear

這個KeyVC,當viewWillAppear的時候,觸發[[Jason client] attach:self],這個函數內會調用[self reload]來進行網絡數據拉取,剛剛說的rootUrl實際上是一個json網絡文件(也能夠設置成bundle內文件),換句話說這個vc的json文件能夠每次從網絡上拉取最新的json文件來實現動態更新的(跟網頁其實是同樣的),這個過程就是觸發網絡框架AF去拉取最新的json

  • AFNetworking download

在網絡數據拉取回來後,會通過一系列的處理,包括請求異步的其餘相關json(像不像異步請求其餘css),把請求到的json字典通過JasonParser這個類的一些其餘處理最後生成最終的Dom字典(Dom這個詞寫在Jason drawViewFromJason的源碼裏,源碼就將這個數據字典的變量起名叫dom,可見他作的和網頁工做原理是一個思路)

  • Jason drawViewFromJason 進行主線程渲染

找到Jason類的drawViewFromJason:函數,這纔是咱們DSL之因此能渲染成界面的最重要的一步,前面都是一直在下載DSL,處理DSL,結果就是json生成了最終須要的元數據字典--Dom字典,這一步就是將DSL轉變成App界面

Dom字典生成界面的過程

簡單的看看這個流程都分別依次調用了哪些函數,不一一講解了,最後咱們挑最有表明的進行說明。

  • [Jason drawViewFromJason:DomDic]

  • [JasonViewController reload:DomDic]

  • Set Stylesheet //CSS

  • [JasonViewController setupSections:DomDic]

  • [JasonViewController setupLayers:DomDic]

setupSections與setupLayers基本上涵蓋了頁面主元素的全部渲染方式

先以簡單的setupLayers的代碼邏輯舉例,先按着約定的標籤從Dom字典中有目的的讀取須要的數據字段Layers,循環遍歷Layers字段數組下的全部數據,每一次都先判斷子節點的Type屬性,若是Type寫了Image,就會建立UIImageView,若是Type寫了Label,就會建立UILabel,根據子節點其餘屬性一一設置不一樣的UIView的屬性,最後AddSubview到界面上。(我會略過大量實際代碼,以僞代碼形式進行說明,實際代碼能夠看源碼查看)

NSArray *layer_items = body[@"layers"];
NSMutableArray *layers = [[NSMutableArray alloc] init];
//循環遍歷Dom樹下的layer字段
if(layer_items && layer_items.count > 0){
    for(int i = 0 ; i < layer_items.count ; i++){
        NSDictionary *layer = layer_items[i];
        layer = [self applyStylesheet:layer];
        //設置Css

        //判斷type字段是否爲image,是否有image url
        if(layer[@"type"] && [layer[@"type"] isEqualToString:@"image"] && layer[@"url"]){

            //NEW一個UIImageView

            //設置UIImageView的style

            //設置UIImageView的 image URL

            //將UIImageView Add subview

            //異步拉取圖片回來後,經過style,運算UIImageView的frame


        } 
        //判斷type字段是否爲label,是否有text
        else if(layer[@"type"] && [layer[@"type"] isEqualToString:@"label"] && layer[@"text"]){

            //NEW一個TTTAttributedLabel

            //設置TTTAttributedLabel的style

            //設置文本

            //addSubview
        }
    }
}複製代碼

再說說setupSections,他其實充分利用了tableview的能力,首先將Dom字典下的sections字段進行保存與整理,而後並不馬上進行渲染,而是直接調用[UITableview reloadData],觸發heightForRowAtIndexPathcellForRowAtIndexPath。(我會略過大量實際代碼,以僞代碼形式進行說明,實際代碼能夠看源碼查看)

heightForRowAtIndexPath獲取cell高度

//取出indexPath.section對應的dom節點數據
NSArray *rows = [[self.sections objectAtIndex:indexPath.section] valueForKey:@"items"];
//取出indexPath.row對應的dom節點數據
NSDictionary *item = [rows objectAtIndex:indexPath.row];

//取出樣式屬性
item = [JasonComponentFactory applyStylesheet:item];
NSDictionary *style = item[@"style"];

//經過JasonHelper傳入style[@"height"]樣式屬性計算寬高
//一些樣式算法算出
return [JasonHelper pixelsInDirection:@"vertical" fromExpression:style[@"height"]];複製代碼

cellForRowAtIndexPath獲取cell

NSDictionary *s = [self.sections objectAtIndex:indexPath.section];
NSArray *rows = s[@"items"];
//獲取對應的Dom節點數據
iNSDictionary *item = [rows objectAtIndex:indexPath.row];
//渲染豎着滑的CELL
//只支持SWTableViewCell這一種客戶端預先寫好的這種通用cell
//支持Dom節點循環內嵌stackview,按着內嵌形式,橫豎佈局都支持
//stackview內的子元素經過JasonComponentFactory建立對應的UIKit UIView
//建立方式如同layer,判斷type等於'image'建立UIImageView,判斷等於'text'建立UILabel
//frame經過style等字段,進行系統autolayout計算
return [self getVerticalSectionItem:item forTableView:tableView atIndexPath:indexPath];複製代碼

上面講的其實力度很粗,而且不少代碼沒有詳細展開,其實目的是讓你們發現,Jasonette的源碼持續在幹一件事情:

  • 從Dom字典中,讀取約定好的固定字段

  • 循環遍歷Dom字典,遍歷全部設計數據

  • 而後用字符串匹配去判斷每一個節點的key與值,指引OC代碼應該怎麼調用

    • 匹配出label就建立UILabel

    • 匹配出iamge就建立UIImageView

    • 匹配出style就調用autolayout賦值屬性進行autolayout計算,或者進行自行算法計算。

Jasonette的DSL工做特色就是這樣,先從設計師給出的元數據入手,把全部的元數據抽象抽離出來,約定成固定的標籤與值,而後客戶端一一遍歷整個Dom元數據的節點,一一解讀這些標籤與值,走入對應的客戶端代碼,從而調用對應的客戶端代碼功能。

客戶端的這套框架寫完以後,之後在寫全新的界面,實際上是無需再重複寫一套客戶端代碼,而是直接寫全新的DSL也就是Jasonette的json文件就能夠了。

DSL小結

拿JSON/HTML/CSS舉例子其實,這些都是一種外部DSL,與之對應的還有某些語言支持的內部DSL,這裏也就不展開了

Never's Blog 外部DSL的實現

XML DSL
不少常見的XML配置文件實際上就是DSL,但不是全部的配置文件都是DSL。好比「屬性列表」和DSL是不一樣的,那只是一份簡單的「鍵-值對」列表,可能再加上分類。
XML不是編程語言,是一種沒有語義的語法結構。XML是DSL的承載語法,可是它又引入了太多語法噪音—太多的尖括號、引號和斜線,每一個嵌套元素都必須有開始標籤和結束標籤。
自定義的外部DSL也帶來了一個煩惱:它們處理引用、字符轉義之類事情的方式老是難以統一。

因此DSL叫特殊領域語言,離開了爲某一DSL專門開發的語言環境或者代碼框架,DSL是沒法運行的,沒有效果的,沒有正則表達引擎的源碼,你寫出來的正則表達式沒人認識。沒有底層數據庫框架,sql語句就只是一行字符串,無法進行數據管理。沒有Jasonette這個框架,你寫出來json也不可能生成界面,有了Jasonette這個框架,你不按着約定的標籤寫,本身單純的在json裏憑空建立標籤,也是不可能正確生成你想要的東西。

在最後,筆者想說的是,當咱們在某一個領域常常須要解決重複性問題時,能夠考慮實現一個 DSL 專門用來解決這些相似的問題。

from 談談 DSL 以及 DSL 的應用

佈局與排版

既然說到動態界面,那必定得聊屏幕適配,這其實無論是否是動態界面,無論用不用到DSL,作客戶端都要考慮的一點,其實網頁在這方面發展的更完善,畢竟客戶端的屏幕尺寸就那麼幾種,就算安卓碎片化,也比不上PC電腦上,桌面瀏覽器用戶能夠任意伸縮窗口的大小,所以對於在不一樣尺寸的限定屏幕大小(即排版區域)內,把設計出來的元素以最美觀的形式進行展示,這就是佈局與排版。

剛纔在講解DSL,講解Jasonette的時候其實迴避一些問題,咱們光提到了經過Dom的type信息,來建立不一樣的UIView,可是每個UIView應該擺放在屏幕的什麼位置,在哪進行展示,在上面的文章中被一帶而過,有的描述,讀取style字段後分別賦值給對應的autolayout,有的被我說成了進行必定的算法從而算出高度。這背後其實都是佈局與排版的算法。

作iOS客戶端的同窗不少會有感觸,早些年的時候寫絕對座標,那時候iOS的屏幕尺寸還不是太多,用代碼手寫frame進行元素定位,試想一下若是純用frame進行app開發,那麼去開發一套對應的DSL動態界面其實更容易,咱們只須要給每一個字典節點,規定上{x:N,y:N,w:N,h:N}的屬性,而後在框架裏別的跟佈局相關的style都不須要寫了,只須要用xywh生成CGRect,而後調用setFrame就行了,想開發出這樣一種絕對佈局的動態界面框架其實還真是挺簡單的。

到後來有了iPad,有了iPhone5,有了iPhone6,6Plus,iOS的屏幕尺寸變的碎片化,若是繼續使用frame,客戶端同窗開發工做量會變的異常繁瑣,因而在IOS7引入了蘋果的autolayout,引入了VFL語言Visual Format Language。其實VFL也應該算是一種DSL吧,他不是用來繪製出一個個的界面元素,而是用來在繪製前,計算清楚每個元素在動態的屏幕區域下的最終位置。咱們學會了如何寫VFL,或者說咱們學會了如何用masnory這個框架實現autolayout,但咱們並不須要深刻去了解這裏面的排版佈局算法。

須要記住的一點是,最終渲染必定是經過frame去頁面上進行繪製,有了明確的座標才能繪製出UI,手寫frame式的絕對佈局代碼,直接由程序員指定,所以必定是性能開銷最小的,能夠說沒有或者少許的佈局運算開銷直接進行渲染,但在多屏適配的需求下才引入了一整套龐大的佈局算法體系(不必定非得是蘋果的autolayout),引入龐大布局算法的目的是但願根據可排區域動態的計算frame,但並不表明採用自動佈局,就與frame無關,自動佈局算法只是間接的運算出frame再渲染。

佈局排版的流程圖

  • RenderTree parse
    • 瀏覽器內核的方案是
      • 解析HTML,生成Dom
      • 解析CSS,生成style rules
      • attach Render Tree CSS與HTML掛載到一塊兒
    • Jasonette的方案是
      • 反序列化Json,直接生成Dom字典
  • RenderTree layout
    • 從RenderTree RootNode 遍歷
    • 不一樣節點對應調用不一樣layout算法
    • 運算出每一個可顯示界面元素的位置信息
  • RenderTree render
    • 遍歷Tree
    • 渲染

佈局排版信息解析

  • 瀏覽器解析HTML/CSS 生成RenderTree

將HTML文件以字符串的形式輸入,通過解析,生成了Dom樹,Dom樹就比如是iOS開發裏面頁面View的層級樹,可是每一個View/div裏面並無css信息,只寫了每一個div所對應的css的名字

將CSS文件以字符串形式輸入,通過解析,獲得了一系列不一樣名字的style rules,樣式規則

Dom樹上的div並不包含樣式信息,而是隻記錄了樣式的名字,而後從style rules裏找到對應名字的具體樣式信息,Attech到一塊兒,生成了Render Tree渲染樹,此時的渲染樹只是Dom與CSS的合併,他依然不包含真正能夠用於渲染的位置信息,由於他還沒通過佈局排版。

  • Jasonette直接生成RenderTree

網頁將View的層級,與View的樣式進行了分離,View就是HTML,樣式是CSS,可是Jasonette的Json沒有作這樣的分離,Json直接描述的就是view與style歸併到一塊兒的數據,所以在Jasonette通過了parse解析後直接就拿到了樣式與視圖的合體結構信息。咱們在Json裏明顯能夠看到head,footer,layers,sections這種字段其實就是HTML裏面的相似Dom的對象,而style這個字段其實就是CSS裏面的對象。Jasonette的源碼裏直把這個字典起名叫 NSDictionary * dom,其實就能夠感知到,雖然Jasonette使用的是json,可是他的思路跟瀏覽器內核是同樣的。

  • iOS autolayout的操做過程

當你使用代碼執行addsubview的操做的時候,你其實就是在對一個view(一種節點),添加了一個子view(一個子節點),當全部subview添加完成的時候,你已經建立好了一個界面層級樹,你addSubview一個子view之後,會對這個view要麼設置VFL,要麼使用masnory,總之會對這個view設置樣式屬性(其實就是在用oc代碼,attach css),以後在layoutIfNeeded的時候,autolayout開始本身悶頭計算排版

換句話說iOS autolayout與HTML/CSS在解析上的區別是,iOS的佈局是用代碼寫死的,生成一種界面層級樹形結構,而網頁HTML/CSS是用可隨意下發的字符串,進行解析,從而生成了一種界面層級樹形結構(RenderTree)

佈局排版

不管使用的是網頁,仍是Jasonette,仍是iOS autolayout,當咱們拿到沒有通過排版的Render Tree的時候,雖然裏面的節點包含着樣式信息,可是並無具體的繪製位置信息,所以須要從Tree的根節點開始依次遍歷每一個節點,每一個節點都根據本身的樣式信息以及子節點的樣式信息進行排版算法計算。

在排版引擎的設計模式裏(一種設計概念,不是指具體某個排版源碼實現),一個RenderTree上每個節點是一種RenderNode,他多是不一樣的界面元素,甚至是界面容器,每一個RenderNode均可以有本身的layout()方法用於計算本身和本身的子節點的算法,一個position絕對佈局的節點,他及內部的子節點佈局算法layout(),確定與一個listview,tableview那種有規律的排布容器節點佈局算法layout()不同,從根節點rootNode開始,循環遍歷遞歸下去,直到把Tree上的全部節點的位置信息都運行了layout(),就完成了佈局排版。

咱們知道不一樣的節點,是能夠用不一樣的算法進行他與內部子節點的佈局計算的。

拿iOS開發舉例子,咱們徹底能夠同一個頁面內,有的view是用frame方式寫死的絕對佈局,有的view是用masnory進行的autolayout,甚至父view是寫死的絕對佈局,子view是autolayou,或者反過來。

拿瀏覽器CSS來講,瀏覽器內核C++代碼裏一個RenderObject的基本子類之一就是RenderBox,該類表示聽從CSS盒子模型的對象,每個盒子有四條邊界:外邊距邊界 margin edge, 邊框邊界 border edge, 內邊距邊界 padding edge 與內容邊界 content edge。這四層邊界,造成一層層的盒子包裹起來。這種基礎RenderBox有着本身的layout()算法。而在新的CSS裏引入了更多不一樣的佈局方式,好比運用很是普遍的Flexbox彈性盒子佈局,Grid佈局,多列布局等等。

在排版引擎的設計模式裏,若是你想引入一種新的佈局算法,或者一種新的專屬佈局效果鎖對應的佈局計算,你只須要建立一種新的RenderNode,而且實現這種node的layout()函數,你就能夠爲你的排版引擎,持續擴展支持更多的排版能力了

Jasonette是怎麼作的?

jasonette其實根本沒本身實現佈局算法,也沒有抽象出renderNode這種樹狀結構,他直接用原始的Dom字典直接開始遍歷遞歸。

遍歷到layers節點,就調用[JasonLayer setupLayers]函數,內部是本身寫的一套xywh的算法,有那麼點像CSS盒子模型,但簡單的多。

遍歷到sections節點,就調用[JasonViewController setupSections]函數,走系統的tableview的reload佈局,在heightforrow的時候,用本身的一套算法計算高度,而在cellforrow的時候,他使用系統stackview與系統autolayoutAPI進行設置,最後走系統autolayout佈局

Jasonette的佈局過程看起來很山寨,從設計上把Dom字典直接快速遍歷,識別標籤,用if else直接對接到不一樣的iOS代碼裏,有的佈局代碼是一些簡單盒子運算,有的佈局代碼則是直接接入系統autolayout,能夠看出來他從DSL的角度,多快好省的快速實現了一個界面DSL框架,但從代碼架構設計的角度上,他距離完善龐大的排版引擎,從模塊抽象以及功能擴展上,還欠缺很多。

佈局排版的幾種算法

  • 絕對佈局

這就不說了,固定精確的座標,其實不須要計算了

  • iOS autolayout

從 Auto Layout 的佈局算法談性能

Auto Layout 的原理就是對線性方程組或者不等式的求解。

這篇文章寫得很是很是清楚,我就不詳細展開了,簡單的說一下就是,iOS會把父view,子view之間的座標關係,樣式信息,轉化成一個N元一次方程組,子view越多,方程組的元數越多,方程組求解起來越耗時,所以運算性能也會愈來愈底下,這一點iOS 的Auto Layout其實被普遍吐槽,廣受詬病。

  • CSS BOX 盒子模型

傳統的CSS盒子模型佈局,這個前端開發應該是基本功級別的東西,能夠自行查閱

  • FlexBox 彈性盒子

CSS3被引入的更好更快更強的強力佈局算法FlexBox,由於其優秀的算法效率,不只僅在瀏覽器標準協議裏,被普遍運用,在natie的hyrbid技術方面,甚至純native技術裏也被普遍運用。

Facebook的ASDK也用的是Flexbox,一套純iOS的徹底與系統UIKit不一樣的佈局方式

大前端Hybrid技術棧裏,RN與Weex中都用的是FlexBox,阿里的另一套LuaView用Lua寫熱更新app的方案也用的是FlexBox算法

由FlexBox算法強力驅動的Weex佈局引擎

  • Grid 佈局

網格佈局(CSS Grid Layout)淺談

CSS佈局模塊

Grid佈局被正式的歸入了CSS3中的佈局模塊,但彷佛目前瀏覽器支持狀況不佳,看起來從設計上補全了Flexbox的一些痛點。

  • 多列布局

CSS佈局模塊

CSS3的新佈局方式,效果就好像看報刊雜誌那樣的分欄的效果。

渲染

通過了整個排版過程以後,renderTree上已經明確知道了每一個節點/每一個界面元素具體的位置信息,剩下的就是按着這個信息渲染到屏幕上。

  • Jasonette

Jasonette直接調用的addSubview來進行view的繪製,Dom字典遍歷完了,view就已經被add到目標的rootview裏面去了,渲染機制和正常客戶端開發沒區別,徹底交給系統在適當的時候進行屏幕渲染。

  • ReactNative & Weex

ReactNative iOS源碼解析(二)

Weex 是如何在 iOS 客戶端上跑起來的

這兩個Link其實介紹了,RN與Weex也是經過addSubview的方式,調用原生native進行渲染,在iOS上來講就是addSubview

  • WebKit

在繪製階段,瀏覽器內核並不會直接使用RenderTree進行繪製,還會進一步將renderTree處理成LayerTree,遍歷這個LayerTree,將內容顯示在屏幕上。

瀏覽器自己並不能直接改變屏幕的像素輸出,它須要經過系統自己的 GUI Toolkit。因此,通常來講瀏覽器會將一個要顯示的網頁包裝成一個 UI 組件,一般叫作 WebView,而後經過將 WebView 放置於應用的 UI 界面上,從而將網頁顯示在屏幕上。但具體瀏覽器內核內部的渲染機制是怎麼工做的有什麼弊端,還取決於各個瀏覽器的底層實現。

How Rendering Work (in WebKit and Blink)

從這裏面能夠詳細看出來,瀏覽器內核的渲染實際上是能夠作到下面這些多種功能的,但不一樣平臺,不懂瀏覽器內核的支持能力不一樣,不是全部的WebView或者瀏覽器App都是一樣的性能與效果

  • 直接調用平臺的系統GUI API
  • 設計本身的高效的Webview圖形緩存
  • 設計多線程渲染架構
  • 融入硬件加速
  • 圖層合成加速
  • WebGL網頁渲染

仔細想一想,真正到渲染這一步,你須要作的都是操做CPU和GPU去計算圖形,而後提交給顯示器進行逐幀繪製,webview與native其實異曲同工。

native界面?動態? 咱們其實一直在聊的是瀏覽器內核技術

@響馬大叔說過"這實際上是最純正的網頁技術,雖然他是native的"。

本文從Jasonette出發,從這個號稱純native,又動態,又用json寫app的技術上入手,看看這native+動態的巨大吸引力到底有多神奇,挖下來看一看。

咱們看到了和瀏覽器內核一脈相承的技術方案

  • 經過DSL,下發設計元數據信息

  • 構建Dom樹

  • 遍歷Dom樹,排版(計算算法與接入autolayout)

  • 遍歷Dom樹,渲染(addsubview接入系統渲染)

瀏覽器內核

Webkit瀏覽器內核就是按着這樣的結構分爲2部分

  • WebCore

綠色虛線部分是WebCore,HTML/CSS都是以String的形式輸入,通過了parse,attach,layout,display,最終調用底層渲染api進行展示

  • JSCore(本文以前一直沒提)

紅色部分是JSCore,JS以string的形式輸入,輸入JS虛擬機,造成JS上下文,將Dom的一些事件綁定到js上,將操做Dom的api綁定到js上,將一些瀏覽器底層native API綁定到js上

動態界面,其實就是瀏覽器內核的WebCore

整個WebCore不是一個虛擬機,他裏面都是C++代碼,所以HTML/CSS在執行效率上,從原理上講和native是一回事,沒區別。

而咱們今天提到的動態界面,不管是Jasonette仍是Tangram,甚至把xib或者storyboard動態下發後動態展現,用iOS系統API就徹底能夠作到動態界面(滴滴的DynamicCocoa裏面提到把xib當作資源動態下發與裝載),其實都和瀏覽器內涵的WebCore部分是一個思路與設計,沒錯。

  • Jasonette的設計思路和HTML/CSS是一回事
  • iOS的xib/storyboard的設計思路和HTML/CSS是一回事

動態界面,能夠界面熱更新,但不是app功能熱更新

本文從開頭到如今,重點圍繞着WebCore的設計思路,講了N多,可是看到Webkit結構圖的時候,你會發現,有個東西我始終沒提到過--JSCore,但我在開頭提到了一句話

Jasonette牛皮其實有點大,其實只是寫動態界面,徹底不是寫動態App

界面動態這個詞與App功能動態有什麼區別呢?

一個APP不只僅須要有漂亮的界面,還須要有業務處理的邏輯。

  • 一個按鈕點擊後怎麼響應?
    • 是否要執行一些業務邏輯,處理一些數據,而後返回來刷新界面?
    • 是否要保存一些數據到本地存儲?
    • 是否要向服務器發起請求?
  • 服務器請求回來後怎麼作?
    • 是否刷新數據和界面?
    • 發現服務器接口請求錯誤,客戶端作業務處理?

Jasonette號稱是用json開發native app,可是json只是一種DSL,DSL是不具有命令和運算的能力的,DSL被譽爲一種聲明式編程,但這些業務邏輯運算,DSL這種領域專用語言是不可能知足的,他須要的是通用編程語言。

所以Jasonette對點擊事件的處理,其實就是一種路由,json的對象裏面有個標籤約定爲action,action的值是一個url字符串,url指向另外一個界面的json文件,也就是說,DSL能夠把這個view的點擊事件寫死,一旦發生點擊,固定會跳轉到url所指向全新的json頁面,換句話說,這就是網頁開發的url跳轉href字段。

換個說法你就理解了,Jasonette在技術上至關於用iOS的native代碼,仿寫了一個處於刀耕火種的原始時代的瀏覽器內核思路,一個尚未誕生js技術,只是純HTML的超文本連接的上個世紀的瀏覽器技術。那個時候網頁裏每個超連接,點進去都是一個新的網頁。

因此這不叫App功能動態,充其量只是界面動態

JSCore的引入給瀏覽器內核注入了動態執行邏輯腳本代碼的能力,先不說腳本引擎執行起來效率不如native,但腳本引擎至少是一個通用編程語言,通用編程語言就有能力執行動態的通用代碼(JS/LUA等),通用代碼比DSL有更強大的邏輯與運算能力,所以能夠更加靈活的擴展,甚至還能夠將腳本語言對接native,這就是webkit架構圖裏提到的jsbinding。

將腳本語言對接到本地localstorage,js就有了本地存儲能力,將腳本語言對接到network,js就有了網絡的能力,將腳本語言對接上dom api,js就有了修改WebCore Dom樹,從而實現業務邏輯二次改變界面的能力。

所以ReactNative & Weex 能夠算做App功能動態,他們不只僅巨有WebCore的能力,同時還巨有JSCore的能力(這裏面其實有個區別,瀏覽器內核的WebCore是純native環境C++代碼,不依賴js虛擬機,但RN與Weex負責實現WebCore能力的代碼,都是js代碼,是運行在虛擬機環境之下的,但他們的渲染部分是bridge到native調用的系統原生api,有興趣看我寫的RN源碼詳解吧,ReactNative iOS源碼解析(一)ReactNative iOS源碼解析(二)

阿里的LuaView我沒細看過源碼,但其實內部機制和RN&WEEX沒啥區別,用的是FlexBox排版,可是選用的是Lua Engine這個腳本引擎,而非JSCore

在native動態化的道路上,不論你們走哪條路,有一個共識仍是你們都找到了的,那就是看齊Web的幾個標準。由於Web的技術體系在UI的描述能力以及靈活度上確實設計得很優秀的,並且相關的開發人員也好招。因此,若是說混合開發指的是Native裏運行一個Web標準,來看齊Runtime來寫GUI,並橋接一部分Native能力給這個Runtime來調用的話,那麼它應該是一個永恆的潮流。

from 「站在10年研發路上,眺望前端將來」:讀後感

雖然有點扯遠了,可是這句話確實又回到響馬叔的那個思路,Jasonette用json寫出native app,他的思路依然是web思路,RN用js寫出native app,也不能改變他一整套web技術的基因。一個界面最終渲染是以native系統級Api實現,並不能說明什麼,渲染只是龐大Web內核技術的末端模塊,把末端渲染模塊換成native的,其實說明不了什麼。

webview性能真的比native慢不少麼?

這裏就要強調一下了,瀏覽器內核在界面這塊是純C++實現,沒有使用任何虛擬機,所謂的瀏覽器內核下的Dom環境是純C++環境,也就是純native環境,因此瀏覽器在單次渲染性能上,不見得比native慢。

CSS的佈局排版兼容不少種佈局算法,有些算法在保證前端開發人員以高素質高質量的開發前提下,一樣的界面,其性能是徹底可能碾壓autolayout的,因此單說佈局這塊,webview也不見得慢。

webview的渲染的時候還存在不少異步資源加載,但這個問題是動態能力帶來的代價,啥都遠端實時拉最新的資源固然會這樣,若是在App下以hybrid的形式,內置本地靜態資源,經過延遲更新本地資源緩存的方式,設計hybrid底層app框架,那麼這種開銷也能減小。更況且瀏覽器新技術PWA也好SW也好都從瀏覽器層面深度優化了WAP APP的資源與緩存。

webview性能慢的緣由不少,多方面綜合來看確實很容寫出性能不佳的頁面,但話也不能絕對了,web技術所帶來的靈活多變,是會給業務帶來巨大收益的。在須要靈活多變,快速響應,敏捷迭代的業務場景下,web技術(泛指這類用web的思路作出來的範hybrid技術)所帶來的優點也是巨大的

動態界面沒那麼神祕,意義並不在技術實現

Jasonette寫了這麼多,雖然沒有深度剖析每一行源碼,但把他的實現思路講解了一下,其實本身實現一個動態界面也不是不能夠。

咱們的工做業務須要深度處理文字,咱們也有一套跨平臺的C++排版引擎內核,思路是一脈相承的,區別是文字排版會比界面區塊盒子排版更復雜的多,用的也是json當作DSL,可是咱們利用咱們的文字排版引擎,去實現相對簡單的各類在native系統上的什麼圖片環繞,圖文繞排,瀑布流界面UI等,其實很是的容易,甚至仍是跨平臺的(比native代碼實現要容易的多)。就連Jasonette代碼裏也就只支持section(tableview佈局)和layer(盒子模型)2中常見形式,複雜頁面同樣實現不了。

可是!可是!可是!

DSL是領域專業語言,DSL就註定巨有着侷限性,你爲本身的排版引擎設計出一套DSL規則,就算都使用的是json,那又如何,新來的一我的能很快上手寫出複雜頁面?DSL的規則越龐大,引擎支持的能力越強,越表明着DSL的學習成本直線加大。

HTML/CSS已經發展成爲一個國際標準,甚至是一種被普遍傳播和學習的DSL,所以他有着不少技術資料,技術社區,方便這門語言的發展,而且隨着應用越廣,語言層面的抽象愈來癒合理,擴展能力也愈來愈強。

可是你本身設計出來的DSL能走多遠?能應用多遠?

  • 學習成本大,哪怕只是在本身業務內,也很大的,須要有效的創建文檔說明,維護業務迭代帶來的功能變化,還要給新來的同事培訓如何寫這種DSL。

  • 應用範圍小,想應付本身一個業務,可能初步設計出來的接口和功能就知足需求了,但也只能本身使用,若是想推廣,必然會帶來更大的維護成本,須要更加精細化合理化的API設計,擴展性設計

  • 人員的遷移成本大,DSL的特色是會讓寫DSL的人員屏蔽對底層代碼的理解,甚至一些初中是爲了能給一些不會編碼的專業領域人員學習和運用,若是編程的人員長時間寫這種專有的DSL,遷移到別的公司之後,該公司不用這種DSL,那麼這些技能就完全廢掉,若是開發者自身不保持一些對底層源碼的自行探索,那麼換工做將會帶來很大的損失

前端人員在寫各類HTML/CSS的時候,想要深入理解透其中的做用機制,也是須要深刻到瀏覽器內核層面去了解內部機制的

因此我以爲天貓的Tangram,是很值得尊敬的,由於想作出一個動態界面框架,沒那麼難,想作大,作到通用性,擴展性,作到推廣,作到持續維護,是很是艱難的,真的很贊!

參考文獻

談談 DSL 以及 DSL 的應用(以 CocoaPods 爲例)

DSL(五)-內部DSL vs 外部DSL (N篇系列文章)

由FlexBox算法強力驅動的Weex佈局引擎

從 Auto Layout 的佈局算法談性能

CSS佈局模塊

走進Webkit

WebCore中的渲染機制(一):基礎知識

理解WebKit和Chromium: WebKit佈局 (Layout)

瀏覽器渲染原理簡介

ReactNative iOS源碼解析(二)

Weex 是如何在 iOS 客戶端上跑起來的

How Rendering Work (in WebKit and Blink)

「站在10年研發路上,眺望前端將來」:讀後感

ReactNative iOS源碼解析(一)

ReactNative iOS源碼解析(二)

相關文章
相關標籤/搜索