VirtualView 的重構之路(二)
前言
VirtualView 是 Tangram 2.0 中解決動態化下發新元件的一個方案。具體的介紹可以參照《貓客頁面內元件的動態化方案-Tangram 2.0》或者開源專案 README,Tangram 2.0 整體開源庫列表如下:
iOS
Android
本系列的前一篇
VirtualView iOS 模板載入功能實現詳解——VirtualView 的重構之路(一)
有關 VirtualView 中支援的簡易字串表示式
VirtualView 用 .out 二進位制模板檔案描述元件模板的內容(具體內容可以參照本系列前一篇文章)。原定計劃中是要將表示式預編譯為二進位制中間碼的,不過這部分功能還沒有完成。所以目前是把表示式直接當做字串儲存在模板檔案裡的,這就需要我們實現對應的解析方法,來實現表示式功能。
VirtualView 中支援兩種基礎的表示式:
資料訪問表示式
${data}
${data[0]}
${data.name}
${data.list[0].title}
複製程式碼
用資料訪問表示式可以讀取後續繫結到 VirtualView 元件的資料裡的元素,主要支援了字典和陣列兩種基本資料結構。
舉個例子,如果我們給一個 NText 書寫以下的 XML:
<NText text="${data.items[0].title}">
複製程式碼
然後再繫結對應的資料:
{
"data" : {
items: [
{@"title" : @"aaa"},
{@"title" : @"bbb"}
]
}
}
複製程式碼
那麼 NText 的 text 屬性就會讀取到 @"aaa"。
三元表示式
@{條件表示式 ? 條件為真表示式 : 條件為假表示式}
@{${title} ? ${title} : unknow}
@{${imageUrl} ? visible : gone}
@{${highlight} ? ${highlightColor} : ${normalColor}}
複製程式碼
這種一般用於需要書寫預設值,或者需要進行條件判斷取不同值的場合。三元表示式的三段都是可以用資料訪問表示式的,但是需要注意的是目前的底層設計上不支援複雜的表示式巢狀。
三元表示式在條件表示式的值為 nil 或者為字串 @"false" 時會呼叫條件為假表示式取值,否則會呼叫條件為真表示式取值。
舉個例子,如果我們想要圖片僅在有 imageUrl 欄位的情況下才進行展示:
<NImage imageUrl="${imageUrl} visibility="@{${imageUrl} ? visible : gone}">
複製程式碼
舊版表示式邏輯設計
舊版的表示式邏輯設計中是把字串解析成陣列或者字典形式,例如 ${data.list[0].title}
將被解析成這麼一個結構:
[
{"key" : "data", "index" : "-1"},
{"key" : "list", "index" : "0"},
{"key" : "title", "index" : "-1"}
]
複製程式碼
這個結構是個固定格式,而且 key 是個必選值,所以就導致了 ${data[0][2][1]}
這種連續陣列元素讀取無法實現。
這個結構被其它模組儲存,在需要用表示式取值時,再利用這個結構進行取值。
相信讀過本系列第一篇的大家也大概發現了,這樣設計可能存在的一些小問題:
1. 資料交由其它模組儲存且不易做驗證
表示式解析出的結構是個基礎的字典資料,很容易被修改,所以在使用結構時還要做對應的驗證保證其正確,增加了程式碼複雜性。這部分資料其實設計成固定的格式更容易驗證。而且更深層次來說,其它模組其實壓根不應該在意這個資料的格式,應該想辦法讓資料的格式對其它模組透明。
2. 表示式實現邏輯分散到了兩處
解析結構和用結構讀取資料的邏輯分散到兩處,沒有形成一個獨立的模組。這個倒不是什麼大的問題,在舊設計基礎上,把邏輯拷貝到一個類中都做成靜態 Helper 方法都是可以的。
新版表示式邏輯設計
1. 把表示式設計成只可以生成及取值的結構
參照 VirtualView-iOS 庫裡的 VVExpression 類:
@interface VVExpression : NSObject
+ (nullable VVExpression *)expressionWithString:(nonnull NSString *)string;
- (nullable id)resultWithObject:(nullable id)object;
@end
複製程式碼
裡面只包含了兩個方法:一個用於建立 VVExpression 例項的工廠方法,一個用表示式取值的方法。
這樣最大程度的保證了內部邏輯完全對外透明,外部只要能用表示式知道怎麼用就可以。
2. 變數表示式(資料訪問表示式)的遞迴解析
首先要跟大家說一下遞迴函式設計的思路,因為我在閱讀很多程式碼的時候都發現遞迴函式被寫得十分難理解。一個遞迴函式最重要的一點就是終止條件,所以寫遞迴之前一定要想清楚一個遞迴怎麼終止。如果沒有想清楚的話輕則沒有拿到想要的結果導致錯誤,重則迴圈遞迴程式崩潰。然後遞迴的邏輯控制方式有兩種:
上層控制進行繼續遞迴的條件
這種遞迴的遞迴邏輯控制大致就是如果滿足指定條件,則繼續進行遞迴。
比較常見的就是樹的遍歷,參照以下示意程式碼:
+ (void)printTree:(Tree *)tree
{
print(tree);
if (tree.leftTree) {
[self printTree:tree.leftTree];
}
if (tree.rightTree) {
[self printTree:tree.rightTree];
}
}
複製程式碼
這個遍歷的終止條件就是子樹為空則停止繼續遞迴。
節點自身控制終止遞迴的條件
有明確的終止條件,一般用於計算某些結果,在滿足終止條件時直接返回固定值。
比較常見的例子是計算階乘,階乘就是 n! = 1 * 2 * 3 * ... * (n-1) * n
,這個大家應該還記得吧?那麼階乘可以用 n! = n * (n-1)!
來表達,這就是階乘的遞迴表示式。但是要注意的是如果我們按照這個定義的話:
1! = 1 * 0! = 1 * 0 * (-1)! = ...
複製程式碼
這就沒完沒了了,會一直遞迴到 CPU 爆炸。所以我們要明確個終止條件,這裡我們就定義好 1! = 1
作為終止條件:
+ (uint)factorial:(uint)num
{
if (num == 0) {
return FactorialZeroResult; // 0 的階乘值
}
if (num == 1) {
return 1;
}
return num * [self factorial:num - 1];
}
複製程式碼
設計變數表示式的資料結構
@interface VVVariableExpression ()
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, strong) VVExpression *nextExpression;
@end
複製程式碼
可以看到結構其實和舊的設計類似。這裡用一個 nextExpression
實現了一個連結串列結構方便進行遞迴。而 key
不再是必選值,每一層要麼用 key
從字典取值要麼用 index
從陣列取值,就是說它們現在是互斥的。
遞迴的解析邏輯
首先如果表示式最外層是從字典取值的話,我會在前面加一個".
",這樣是為了方便遞迴解析。拿前文的例子來看:
資料最終被拆分成 4 段,我們的遞迴每一次解析其中的一段,然後如果有剩餘的段,則新建一個 nextExpression 解析剩餘的段。虛擬碼如下:
+ (VVVariableExpression *)解析表示式:(NSString *)string
{
VVVariableExpression *expression = 解析第一段表示式的key或者index;
從string中剔除第一段表示式的內容;
if (expression有效 && string仍不為空) {
expression.nextExpression = 用剩餘string解析表示式遞迴;
}
}
複製程式碼
具體解析的程式碼請參照 VVVariableExpression 類。
3. 變數表示式遞迴取值過程
既然表示式的資料結構是個連結串列,那麼取值時一樣是用遞迴比較方便:
+ (id)表示式取值:(id)object
{
id nextObject = 從object裡用本表示式的key或者index取值;
if (nextExpression) {
return 用nextExpression對nextObject進行表示式取值遞迴;
} else {
return nextObject;
}
}
複製程式碼
依然用最上面的例子,配合上文的 JSON,遞迴的圖示大致如下:
最外層呼叫的時候相當於最左邊的呼叫,遞迴到下一層的時候就一層層簡化,最終相當於只是用 ${title}
表示式從 {@"title" : @"aaa"}
裡去取資料一樣。
4. 表示式的字串拆分邏輯
目前表示式的字串拆分邏輯很簡單,從前向後尋找 "${" 或者 "@{",再從後向前尋找 "}",然後把中間的內容解析出來。當然三元表示式裡要出現的 "?" 和 ":" 也只是簡單的從前向後尋找。所以如果出現複雜的巢狀邏輯,或者字串內部出現 "${}?:" 這些字元,解析都是會出問題的。
更強大的字串解析功能
如果要實現更強大的解析功能,可以考慮採用正規表示式進行復雜的匹配,當然更根本的方式是自己實現詞法分析器和語法分析狀態機等。不過這些目前來說對於 VirtualView 來說有點略重了,而且後續我們打算把表示式先轉換成中間碼,所以短期內我們不太打算加入更強大的表示式解析功能。
總結
至此 VirtualView-iOS 裡的字串表示式簡易實現給大家介紹的差不多了,希望對大家有幫助。
後續還會介紹更多的 VirtualView 實現細節,敬請期待。