1. 程式人生 > IOS開發 >iOS--GICDataBinding | 基於NSProxy開發的資料繫結庫--支援JS表示式

iOS--GICDataBinding | 基於NSProxy開發的資料繫結庫--支援JS表示式

簡介

GICDataBinding是一款基於NSProxy開發的資料繫結庫,支援資料繫結事件繫結(覺得好的各位,不要吝嗇您的star)有如下特色功能:

  1. 支援JS表示式

    @"'姓名:'+$.name.split('').reverse().join('') + ',性別:'+($.isMale?'男':'女')"
    複製程式碼
    1. 表示式僅支援單一表達式,如果表示式字串中出現多個表示式,可能會出現意外。
    2. 你可以在單一表達式中呼叫任意JS中的方法,甚至呼叫你預先注入的方法。這樣一來就為基於資料繫結開發的功能增加了無限可能。你可以直接將一些以前在ViewModel中定義的方法直接注入到JSCore中,使得可以直接在JS表達中呼叫這些方法
    3. 你可以直接在JS表示式中對資料來源中的屬性做計算,然後將結果返回。

    靈活基於JSCore開發、注入各種方法Class將會使得開發某些功能變得異常的簡單。甚至如果你的部分UI是基於Texture這樣的支援自動佈局的庫開發的話,那麼對於構建UI這樣的任務變得異常的簡單。

  2. 單向繫結

  3. 雙向繫結

    雙向繫結在本質上還是基於單向繫結的,但是GICDataBinding對某些UI元件進行了封裝,使得可以直接一行程式碼就能完成雙向繫結。類似於前端vue中的model

  4. 支援對NSMutableArray進行觀察。

    當陣列內容變更後,你可以得到相應的回撥。這樣一來你可以開發出類前端VUE那樣的自動根據陣列內容變更,從而update UI的功能。

  5. 支援事件繫結重點

    當前對於UIControl已經實現了相關的事件繫結。其他的事件開發者可以自己去實現。實現的方法也是很簡單的

    這裡面的技術基礎就是,基於NSProxy實現對方法呼叫的攔截,從而可以實現類似方法交換的目的。也就是說這裡可以不通過方法交換技術就能實現類似的功能需求。PS:有興趣可以看下這部分原始碼,我相信一定會讓你有所收穫。

  6. 當然也支援Swift開發。但是要求Swift中的資料類必須是NSObject子類。

安裝

pod 'GICDataBinding'
複製程式碼

github地址:github.com/ghwghw4/GIC…

使用方法

資料模型

所有的資料繫結功能的前提是一個可以被觀察

的物件,就像KVO那樣的。但是GICDataBinding不是基於KVO開發的,而是基於NSProxy開發的,因此在進行資料繫結以前,需要對你的資料來源做一個轉換,將資料來源變成可觀察物件,這一切的原理基礎是基於NSProxy實現的。

  1. 首先你要有一個數據類,比如UserInfo這樣的資料模型

  2. 資料類必須實現GICObserverProtocol協議,這個協議其實是一個空協議,僅僅是用來標記該類是可觀察的。由於GICDataBinding支援巢狀的,因此所有資料模型中的物件想要可以被觀察,那麼都需要實現GICObserverProtocol協議。

    @interface TestData : NSObject<GICObserverProtocol>
    @property(nonatomic,copy)NSString *name;
    @property(nonatomic,assign)NSInteger age;
    @property(nonatomic,assign)BOOL isMale;
    @property(nonatomic,strong)UserAddress *address;
    @property (nonatomic,strong)NSDictionary *dict;
    @property (nonatomic,strong)NSMutableDictionary *dict2;
    @property (nonatomic,strong)NSArray *array;
    @property (nonatomic,strong)NSMutableArray *mutArray;
    @property(nonatomic,assign)NSInteger timeTick;
    +(instancetype)testData;
    @end
    複製程式碼
  3. 對資料模型呼叫gic_observer方法,你會獲得一個可以被觀察的資料模型。

     TestData *data = [[TestData testData] gic_observer];
    複製程式碼

資料繫結

JS表示式中的$表示資料來源本身,因此如果你想要訪問資料來源的某個屬性或方法,那麼必須使用$來訪問

  1. 普通的單向繫結。

    [btn gic_addBinding:createDataBinding(theme,@"$.backgroundColor",^(UIButton *target,id newValue) {
       [target setTitleColor:newValue forState:UIControlStateNormal];
    })];
    複製程式碼

    這樣當資料來源中的backgroundColor屬性改變的時候就會觸發回撥

  2. 直接將表示式繫結到目標物件的屬性上。

    [lbl gic_addBinding:createPropertyDataBinding(user,@"'計數:'+$.timeTick",@"text")];
    複製程式碼

    通過createPropertyDataBinding可以建立一個屬性繫結,將表示式'計數:'+$.timeTick的結果自動繫結到UILabletext屬性。

    注意:createPropertyDataBinding 在內部實現的時候基於setValue:forKey來實現的,因此確保這個屬性是支援KVC的。

    如果這個屬性不支援,那麼使用第一種方法也能達到資料繫結目的。

  3. 雙向繫結。

    UISwitch *sw = [[UISwitch alloc] initWithFrame:CGRectMake(10,CGRectGetMaxY(btn.frame)+10,100,44)];
    [sw gic_towwayBinding:user propertyName:@"isMale"];
    [self.view addSubview:sw];
    複製程式碼

    你可以通過gic_towwayBinding來建立一個雙向繫結。propertyName表示的是資料來源中的屬性名稱。也就說,當資料來源中的isMale改變的時候,UISwitchisOn也會跟著改變。而當UISwitchisOn被改變的時候,資料來源中的isMale屬性也會跟著改變。但這裡你不用擔心會陷入死迴圈,類庫已經做了比較處理,如果兩次變更的value是一樣的,那麼不會重複觸發。

  4. NSMutableArray進行觀察。

    第一步當然是需要將array轉換成可觀察物件了。通過呼叫gic_observer來實現。。

     arrayObserve.changedBlock = ^(NSMutableArray *mutArray,GICMutableArrayChangedType changedType,NSArray *params) {
                    switch (changedType) {
                        case GICMutableArrayChangedAddObject:
                            NSLog(@"GICMutableArrayChangedAddObject:%@",params[0]);
                            break;
                        case GICMutableArrayChangedRemoveObject:
                            NSLog(@"GICMutableArrayChangedRemoveObject:%@",params[0]);
                            break;
                        case GICMutableArrayChangedRemoveAllObjects:
                            NSLog(@"GICMutableArrayChangedRemoveAllObjects");
                            break;
                        case GICMutableArrayChangedRemoveLastObject:
                            NSLog(@"GICMutableArrayChangedRemoveLastObject:%@",params[0]);
                            break;
                        case GICMutableArrayChangedSortArray:
                            NSLog(@"GICMutableArrayChangedSortArray:%@",mutArray);
                            break;
                        case GICMutableArrayChangedInsertObject:
                            NSLog(@"GICMutableArrayChangedInsertObject:%@,index:%@",params[0],params[1]);
                            break;
                        case GICMutableArrayChangedReplaceObject:
                            NSLog(@"GICMutableArrayChangedReplaceObject:%@,params[1]);
                            break;
                        default:
                            break;
                    }
                };
    複製程式碼

    目前GICDataBinding支援的課觀察方法的列舉都已經在上面列出。

  5. 在表示式中呼叫注入的JS方法。

    [GICJSContextManager manager].jsContext[@"customFunc"] = ^(JSValue*value){
       //這裡以將Color 轉換成字串為例
       UIColor *color = [value toObject];
       CGFloat r,g,b,a;
       [color getRed:&r green:&g blue:&b alpha:&a];
       return [NSString stringWithFormat:@"r:%f,g:%f,b:%f",r,b];
    };
    // 資料繫結表示式直接呼叫js方法
    [lbl gic_addBinding:createPropertyDataBinding(theme,@"'顏色轉換:'+customFunc($.titleColor)",@"text")];
    複製程式碼

    注入JS方法有兩種方式

    1. 直接如下上面的方法,採用block方式注入

    2. 呼叫 [[GICExpresionCalculator calculator].jsContext evaluateScript:jsString] 注入JS指令碼

事件繫結

GICDataBinding中的事件繫結只能呼叫JS程式碼,但是得益於JSCore能夠直接呼叫原生代碼,也就意味著你也可以直接在事件繫結中呼叫原生代碼。

在做事件繫結前,首先你要有一個ViewModel。同樣,這個ViewModel需要實現GICObserverProtocol協議。比如下面:

@interface TestViewModel : NSObject<GICObserverProtocol>
@property (nonatomic,copy)NSString *v1;
@property (nonatomic,assign)NSInteger v2;
/// 所有可以被JS呼叫的方法,都必須帶有一個引數,否則無法呼叫
-(void)onButtonClicked:(id)param;
@end
複製程式碼
  1. 初始化ViewModel

     testViewModel = [[[TestViewModel alloc] init] gic_observer];
    複製程式碼
  2. 繫結按鈕的點選事件。

     [btn gic_addEventBinding:[GICEventBinding bindingWidthDataSource:testViewModel expression:@"$.onButtonClicked(`${$.v1}----${$.v2}`)"] forControlEvents:UIControlEventTouchUpInside];
    複製程式碼

    從這裡你可以看到,當按鈕點選事件觸發後,就會執行JS指令碼

    $.onButtonClicked(`${$.v1}----${$.v2}`)
    複製程式碼

    從這裡看到,是呼叫的testViewModel中的onButtonClicked方法,並且傳入字串引數。

  3. 繫結事件中直接設定資料來源的value

     [btn gic_addEventBinding:[GICEventBinding bindingWidthDataSource:testViewModel expression:@"$.v2++"] forControlEvents:UIControlEventTouchDown];
    複製程式碼

    這裡每當點選事件觸發後,就會對資料來源的v2屬性+1。如果有其他地方綁定了v2屬性,那麼也會同時更新資料繫結

  4. 事件繫結直接呼叫原生代碼。

    [btn gic_addEventWatch:^(NSArray * _Nullable params) {
       NSLog(@"點選了");
    } forControlEvents:UIControlEventTouchUpInside];
    複製程式碼

    這裡其實已經不算事件綁定了,可以說是事件代理。GICDataBinding基於NSProxy實現了一套可以直接block回撥按鈕事件的庫。現在你無需RAC就能實現同樣的通能。事實上,你可以對任意物件的任意方法進行watch,在某些情況下,你壓根就無需方法交換就能實現類似方法交換的功能,而且這種基於NSProxy實現呼叫攔截的過程是安全、不衝突的,不會出現方法交換可能引起的crash問題(多次交換)。

注意:

所有可以在事件繫結中被呼叫的ViewModel中定義的方法,必須帶有一個引數,哪怕這個引數你不會用到,而且目前只支援帶有一個引數的方法。如果你需要傳入多個引數,目前唯一的方法是以陣列的方式傳入

說明

不管是資料繫結還是事件繫結,因為都是基於JS表示式來實現的,因此想要熟練的將資料繫結應用到專案中,還需要您對JavaScript有一定的瞭解,另外GICDataBinding的JS引擎是基於JavaScriptCore來實現的,因此如果你想在專案中擴充套件JSCore甚至想要以前端開發一樣,直接在JS中建立ViewModel,那麼需要你對JavaScriptCore有一定的瞭解

應用

基於GICDataBinding資料繫結系統,你可以做一些很多以前實現起來比較複雜的功能。比如:

  1. app 主題(Example 有例子)

    可以直接基於繫結系統,將一些主題元素繫結到提供主題資料的模型。這樣當用戶修改主題的時候,app可以做出實時的改變。

  2. 重新思考ViewModel的定義。將ViewModel JS化

    你現在可以把部分或者整個原來已有的ViewModel移入JSCore,然後通過資料繫結系統直接呼叫

  3. 配合Texture實現整個UI 基於繫結系統的可響應式設計。

  4. HotFix

    由於資料繫結和事件繫結都是採用JS表示式的方式呼叫,因此理論是上可以直接通過下發JS指令碼來動態修復、增加功能的。當然,這僅僅是理論上,真要實現起來還是有很大開發量的