VirtualView iOS 簡易字串表示式的實現

HarrisonXi_發表於2018-03-06

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 從陣列取值,就是說它們現在是互斥的。

遞迴的解析邏輯

首先如果表示式最外層是從字典取值的話,我會在前面加一個".",這樣是為了方便遞迴解析。拿前文的例子來看:

VirtualView iOS 簡易字串表示式的實現

資料最終被拆分成 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,遞迴的圖示大致如下:

VirtualView iOS 簡易字串表示式的實現

最外層呼叫的時候相當於最左邊的呼叫,遞迴到下一層的時候就一層層簡化,最終相當於只是用 ${title} 表示式從 {@"title" : @"aaa"} 裡去取資料一樣。

4. 表示式的字串拆分邏輯

目前表示式的字串拆分邏輯很簡單,從前向後尋找 "${" 或者 "@{",再從後向前尋找 "}",然後把中間的內容解析出來。當然三元表示式裡要出現的 "?" 和 ":" 也只是簡單的從前向後尋找。所以如果出現複雜的巢狀邏輯,或者字串內部出現 "${}?:" 這些字元,解析都是會出問題的。

更強大的字串解析功能

如果要實現更強大的解析功能,可以考慮採用正規表示式進行復雜的匹配,當然更根本的方式是自己實現詞法分析器和語法分析狀態機等。不過這些目前來說對於 VirtualView 來說有點略重了,而且後續我們打算把表示式先轉換成中間碼,所以短期內我們不太打算加入更強大的表示式解析功能。

總結

至此 VirtualView-iOS 裡的字串表示式簡易實現給大家介紹的差不多了,希望對大家有幫助。

後續還會介紹更多的 VirtualView 實現細節,敬請期待。

相關文章