iOS--GICDataBinding | 基於NSProxy開發的資料繫結庫--支援JS表示式
簡介
GICDataBinding是一款基於NSProxy
開發的資料繫結庫,支援資料繫結
和事件繫結
(覺得好的各位,不要吝嗇您的star
)有如下特色功能:
-
支援
JS表示式
@"'姓名:'+$.name.split('').reverse().join('') + ',性別:'+($.isMale?'男':'女')" 複製程式碼
- 表示式僅支援單一表達式,如果表示式字串中出現多個表示式,可能會出現意外。
- 你可以在單一表達式中呼叫任意JS中的方法,甚至呼叫你
預先注入
的方法。這樣一來就為基於資料繫結開發的功能增加了無限可能。你可以直接將一些以前在ViewModel
中定義的方法直接注入到JSCore中,使得可以直接在JS表達中呼叫這些方法 - 你可以直接在JS表示式中對資料來源中的屬性做計算,然後將結果返回。
靈活基於
JSCore
開發、注入各種方法
、Class
將會使得開發某些功能變得異常的簡單。甚至如果你的部分UI是基於Texture
這樣的支援自動佈局的庫開發的話,那麼對於構建UI這樣的任務變得異常的簡單。 -
單向繫結
-
雙向繫結
雙向繫結在本質上還是基於單向繫結的,但是GICDataBinding對某些UI元件進行了封裝,使得可以直接一行程式碼就能完成雙向繫結。類似於前端
vue
中的model
-
支援對
NSMutableArray
進行觀察。當陣列內容變更後,你可以得到相應的回撥。這樣一來你可以開發出類前端
VUE
那樣的自動根據陣列內容變更,從而update UI的功能。 -
支援
事件繫結
。重點當前對於
UIControl
已經實現了相關的事件繫結。其他的事件開發者可以自己去實現。實現的方法也是很簡單的這裡面的技術基礎就是,基於NSProxy實現對方法呼叫的攔截,從而可以實現類似方法交換的目的。也就是說這裡可以不通過方法交換技術就能實現類似的功能需求。PS:有興趣可以看下這部分原始碼,我相信一定會讓你有所收穫。
-
當然也支援
Swift
開發。但是要求Swift
中的資料類必須是NSObject
子類。
安裝
pod 'GICDataBinding'
複製程式碼
github地址:github.com/ghwghw4/GIC…
使用方法
資料模型
所有的資料繫結功能的前提是一個可以被觀察
KVO
那樣的。但是GICDataBinding
不是基於KVO
開發的,而是基於NSProxy
開發的,因此在進行資料繫結以前,需要對你的資料來源做一個轉換,將資料來源變成可觀察物件,這一切的原理基礎是基於NSProxy
實現的。
-
首先你要有一個數據類,比如
UserInfo
這樣的資料模型 -
資料類必須實現
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 複製程式碼
-
對資料模型呼叫
gic_observer
方法,你會獲得一個可以被觀察的資料模型。TestData *data = [[TestData testData] gic_observer]; 複製程式碼
資料繫結
JS表示式中的
$
表示資料來源本身,因此如果你想要訪問資料來源的某個屬性或方法,那麼必須使用$
來訪問
-
普通的單向繫結。
[btn gic_addBinding:createDataBinding(theme,@"$.backgroundColor",^(UIButton *target,id newValue) { [target setTitleColor:newValue forState:UIControlStateNormal]; })]; 複製程式碼
這樣當資料來源中的
backgroundColor
屬性改變的時候就會觸發回撥 -
直接將表示式繫結到目標物件的屬性上。
[lbl gic_addBinding:createPropertyDataBinding(user,@"'計數:'+$.timeTick",@"text")]; 複製程式碼
通過
createPropertyDataBinding
可以建立一個屬性繫結,將表示式'計數:'+$.timeTick
的結果自動繫結到UILable
的text
屬性。注意:createPropertyDataBinding 在內部實現的時候基於
setValue:forKey
來實現的,因此確保這個屬性是支援KVC的。如果這個屬性不支援,那麼使用第一種方法也能達到資料繫結目的。
-
雙向繫結。
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
改變的時候,UISwitch
的isOn
也會跟著改變。而當UISwitch
的isOn
被改變的時候,資料來源中的isMale
屬性也會跟著改變。但這裡你不用擔心會陷入死迴圈,類庫已經做了比較處理,如果兩次變更的value是一樣的,那麼不會重複觸發。 -
對
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
支援的課觀察方法的列舉都已經在上面列出。 -
在表示式中呼叫注入的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方法有兩種方式
-
直接如下上面的方法,採用block方式注入
-
呼叫 [[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
複製程式碼
-
初始化ViewModel
testViewModel = [[[TestViewModel alloc] init] gic_observer]; 複製程式碼
-
繫結按鈕的點選事件。
[btn gic_addEventBinding:[GICEventBinding bindingWidthDataSource:testViewModel expression:@"$.onButtonClicked(`${$.v1}----${$.v2}`)"] forControlEvents:UIControlEventTouchUpInside]; 複製程式碼
從這裡你可以看到,當按鈕點選事件觸發後,就會執行JS指令碼
$.onButtonClicked(`${$.v1}----${$.v2}`) 複製程式碼
從這裡看到,是呼叫的
testViewModel
中的onButtonClicked
方法,並且傳入字串引數。 -
繫結事件中直接設定資料來源的value
[btn gic_addEventBinding:[GICEventBinding bindingWidthDataSource:testViewModel expression:@"$.v2++"] forControlEvents:UIControlEventTouchDown]; 複製程式碼
這裡每當點選事件觸發後,就會對資料來源的
v2
屬性+1
。如果有其他地方綁定了v2
屬性,那麼也會同時更新資料繫結 -
事件繫結直接呼叫原生代碼。
[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
資料繫結系統,你可以做一些很多以前實現起來比較複雜的功能。比如:
-
app 主題(Example 有例子)
可以直接基於繫結系統,將一些主題元素繫結到提供主題資料的模型。這樣當用戶修改主題的時候,app可以做出實時的改變。
-
重新思考
ViewModel
的定義。將ViewModel
JS化你現在可以把部分或者整個原來已有的ViewModel移入JSCore,然後通過資料繫結系統直接呼叫
-
配合
Texture
實現整個UI 基於繫結系統的可響應式設計。 -
HotFix
由於資料繫結和事件繫結都是採用JS表示式的方式呼叫,因此理論是上可以直接通過下發JS指令碼來動態修復、增加功能的。當然,這僅僅是理論上,真要實現起來還是有很大開發量的