iOS: 基於KVO的輕量級iOS雙向數據綁定響應式編程框架

前言:2年前空閒時間玩了Vue.js, 發現利用數據雙向綁定,開發如此輕鬆簡潔,我瞭解iOS也有相似的框架 ReactiveCocoa, ReactiveCocoa有點複雜和笨重,我只須要簡單點的數據綁定,因此寫了一個輕量級的數據綁定,麻煩你們看一下,有問題請指點下git


1.Demo例子:

  • 通常配合MVVM架構使用,主要用於View和ViewModel雙向綁定,也可用於其餘數據雙向綁定
  • 這裏介紹下雙向數據綁定好處:
    1. View.textField 跟 ViewModel.text 綁定,用戶在輸入框textField輸入"Hello World",text也會響應式更新,此時text = @"Hello World", 咱們只要對text進行處理
    2. 若是咱們從網絡獲取Model,Model 轉換並賦值於 ViewModel.text,View也會響應式更新界面, 整個過程都是對ViewModel.text進行操做,不會再去處理View部分

github地址: github.com/shidavid/DV…
其餘例子:利用 DVDataBind 雙向綁定 + MVVM 簡單實現登陸界面github

// 這裏只是展現響應式變化 
DVDataBind
._inout(self.demoModel, @"text")
._inout_ui(self.demoView.textField, @"text", UIControlEventEditingChanged)
._out(self.demoView.label, @"text")

// 點擊button
- (void)onClickForButton:(UIButton *)sender {
    self.demoModel.text = @"Hello World";
}
複製代碼


2.介紹:

  1. 不限定只能 View 與 ViewModel 綁定,只要支持KVC的數據都能雙向綁定
  2. 使用鏈式編程,支持多項綁定
  3. 支持單向數據流/雙向數據流
  4. 支持 字符串,整形,浮點型,布爾類型 之間數據自動轉換 (對象類型除外)
  5. 支持過濾, 限制,轉換, 觀察數組某一位數據變化
  6. 無需繼承基類,無需手動解綁, 當目標對象內存釋放,DataBind自動解綁和釋放內存

3.思路

  1. A 與 B 雙向數據綁定,Ain數據變化更新Aout、Bout數據,Bin同理 編程

  2. 有時候 A 與 B 雙向綁定,B 與 C 雙向綁定, 其實至關於 A、B、C 一塊兒綁定在一條數據鏈Chain上, 每當有一個in數據變化, 發送新數據到Chain上,再由Chain更新全部的out數據 數組

這樣實現單向/雙向數據流 bash

  1. 利用KVO, 數據鏈就至關於Obverse,每一個Observer用一個ChainCode標記,Observer觀察每一個in數據變化,並更新到全部Out數據

4.用法

  • DVDataBind 必須用 _in 或 _inout 開頭, 後面綁定順序前後隨意, 任意組合, 不影響結果
  • _in 只發送新數據,_inout 可接受和發送新數據,_out 只接受新數據
  • 目標對象必須支持KVC
  • 目標對象不能爲nil, property可爲nil
  • Swift也能使用, 只不過更新數據不能直接 object.property = xxx , 須要 object.setValue(xxx, forKey: "property")
1. 普通綁定
/*
  object爲目標對象, property是object擁有的屬性
  object不能爲nil,property可爲nil
*/
._inout(object, @"property")


舉例:
/*
  objectA -> a1, a2
  objectB -> b
  objectC -> c
  a一、a二、b、c 正常狀況爲同一類型, 若是不一樣類型查看下面 "3.轉換"
*/
DVDataBind
._in(objectA, @"a1")
._inout(objectA, @"a2")
._inout(objectB, @"b")
._out(objectC, @"c");
複製代碼
2. UI 綁定
/*
  UI: 支持 UIControlEvents
  property: 經過觸發 UIControlEvents 會產生數據變化的 屬性
*/
._inout_ui(UI, @"property", UIControlEvents)


舉例:
/* 
  view      -> UITextField *textField;
  viewModel -> NSString *text;
*/
DVDataBind
._inout_ui(view.textField, @"text", UIControlEventEditingChanged)
._inout(viewModel, "text");


/*
 view      -> UILabel *label;
 viewModel -> NSString *text;
 UILabel 不支持 UIControlEvents
*/
DVDataBind
._in(viewModel, "text");
._out(view.label, @"text");


/*
 view      -> UISwitch * switch;
 viewModel -> BOOL isON;
*/
DVDataBind
._inout_ui(view.switch, @"on", UIControlEventValueChanged)
._inout(viewModel, "isON");


/*
 view      -> UIImageView *imageView;
 viewModel -> UIImage *image;
 UIImageView 不支持 UIControlEvents
*/
DVDataBind
._in(viewModel, "image");
._out(view.imageView, @"image");


/*
 view      -> UISlider *slider;
 viewModel -> float value;
*/
DVDataBind
._inout_ui(view.slider, @"value", UIControlEventValueChanged)
._inout(viewModel, "value");


/*
 view      -> UIProgressView *progressView;
 viewModel -> float value;
*/
DVDataBind
._inout_ui(view.progressView, @"progress", UIControlEventValueChanged)
._inout(viewModel, "value");


/*
  view      -> UISegmentedControl *segmented;
  viewModel -> int index;
*/
DVDataBind
._inout_ui(view.segmented, @"selectedSegmentIndex", UIControlEventValueChanged)
._inout(viewModel, "index");


/*
 view      -> UIStepper *stepper;
 viewModel -> int index;
*/
DVDataBind
._inout_ui(view.stepper, @"value", UIControlEventValueChanged)
._inout(viewModel, "index");


/*
 view  -> UIButton *button;
 點擊Button改變的是highlighted值,highlighted容易打錯, 仍是建議用 addTarget
*/
DVDataBind
._in_ui(view.button, @"highlighted", UIControlEventTouchUpInside)
._out_key_any(@"自定義", ^{
    // 點擊觸發
});
複製代碼
3.轉換
  • 支持 字符串,整形,浮點型,布爾類型 之間數據自動轉換 (對象類型除外)
/*
普通對象轉換
ClassA objectA -> a;
ClassB objectB -> b;
*/
DVDataBind
._inout_cv(objectA, @"a", ^ClassA *(ClassB *變量) {
    // 處理程序
    return 轉換爲ClassA的數據更新 objectA.a;
})
._inout_cv(objectB, @"b", ^ClassB *(ClassA *變量) {
    // 處理程序
    return 轉換爲ClassB的數據更新 objectB.b;
} );


特殊狀況:
/*
view      -> UITextField *textField;
viewModel -> NSString *text;
viewModel -> int number;
支持 字符串,整形,浮點型,布爾類型 之間數據自動轉換 (對象類型除外)
若是text爲非數字, 則number爲0
*/
DVDataBind
._inout_ui(view.textField, @"text", UIControlEventEditingChanged)
._inout(viewModel, "text")
._inout(viewModel, "number"); 


/*
view  -> UITextField *textField;
view  -> UILabel *label;
viewModel -> NSString *text;
viewModel -> int number;
這裏 更新值有 NSString, int 類型,上面說過這些類型之間自動轉換
viewModel->text 獲取更新值自動轉爲NSString, 處理完返回NSString 再去更新本身
viewModel->number 獲取更新值自動轉爲NSNumber, 處理完返回NSNumber 再去更新本身
*/
DVDataBind
._inout_ui(view.textField, @"text", UIControlEventEditingChanged)
._out_cv(view.label, @"text", ^NSString *(NSString *text) {
    NSString *tempText = [NSString stringWithFormat:@"AAA - %@ - BBB",text];
    return tempText;
})
._inout_cv(viewModel, @"text", ^NSString *(NSString *text) {
    NSString *tempText = [NSString stringWithFormat:@"CCC - %@ - DDD",text];
    return tempText;
} )
._inout_cv(viewModel, @"number", ^NSNumber *(NSNumber *num) {
    int value = [num intValue];
    return @(value + 123456);
});

複製代碼
4.數組
/*
  objectA -> array
  array必須爲NSMutableArray類型, 綁定前必須初始化, 數組可提早賦值, 也能夠爲空
*/
._inout_arr(objectA, @"array", 1)

// 更新數組某位必須該方法
NSMutableArray *pArray = [objectA mutableArrayValueForKey:@"array"];
pArray[0] = @(123456);
pArray[1] = @"Hellow World";  //這裏更改了第1位數據, 響應
pArray[2] = object;
複製代碼
5.取反
// property類型爲BOOL類型
._out_not(objectA, @"property");


舉例:
/*
  view -> UITextField *textField;
  view -> UISwitch * switch;
  view -> UISwitch * switchNot;
 當textField.text長度不爲0,  則switch.on = YES, switchNot.on = NO
 當 switch.on = NO, switchNot.on = YES
*/
DVDataBind
._in_ui(view.textField, @"text", UIControlEventEditingChanged)
._inout_ui(view.switch, @"on", UIControlEventValueChanged)
._out_not(view.switchNot, @"on")
複製代碼
6.輸出Block
  • 支持綁定多個輸出Block, 更新數據不支持自動轉換
// 自定義名不能同樣, 類型爲更新數據類型 (類型、變量可不寫)
._out_key_any(@"自定義名1", ^(Class 變量){
     // 處理代碼1
 })
._out_key_any(@"自定義名2", ^(Class 變量){
     // 處理代碼2
 })
._out_key_any(@"自定義名3", ^{
     // 處理代碼3
 });


舉例:
// 整形、浮點型、布爾類型,必須是NSNumber 類型
._out_key_any(@"自定義名", ^(NSNumber *num){ 
     // 處理代碼
 });

//更新值只是NSString類型
._out_key_any(@"自定義名", ^(NSString *text){  
     // 處理代碼
 });

// 若是更新數據類型多樣,就用id
._out_key_any(@"自定義名", ^(id value){
    //判斷value爲哪一個Class, 進行處理
 });
複製代碼
7.過濾,限制
  • 一個數據鏈只能綁定一個過濾, 更新數據不支持自動轉換
  • 在這裏能夠對數據進行判斷,限制,校驗等等操做
._filter(^BOOL(Class 變量) {  
    // 這裏能夠對數據進行判斷,限制,校驗等等
    return YES/NO; // 返回YES 則正常數據更新, NO不更新
})


舉例:
//更新值只是NSString類型
._filter(^BOOL(NSString *text) {  
    return text.length <= 20; //限制字符串長度爲20
})

//更新值爲整形、浮點型、布爾類型,必須是NSNumber類型
._filter(^BOOL(NSNumber *num) {  
    return [num intValue] <= 100; //限制數字最大爲100
})

//若是更新值爲多類型,例若有NSString,NSNumber, 則寫id
._filter(^BOOL(id value) {  
    //判斷value爲哪一個Class, 進行處理  
    return YES/NO;
})

複製代碼
8.中途增長綁定
// 一開始綁定生成一條數據鏈
DVDataBind
._inout(objectA, @"a")
._inout(objectB, @"b")

// 將objectBB.bb 加入 objectA.a 的數據鏈中,
// objectA.a、objectB.b、objectBB.bb在同一數據鏈上
DVDataBind
._inout(objectA, @"a")
._inout(objectBB, @"bb")

// 至關於
DVDataBind
._inout(objectA, @"a")
._inout(objectB, @"b")
._inout(objectBB, @"bb")
複製代碼
9.解綁
  • 已經支持 當對象內存釋放自動解綁, 無需手動解綁, 若是想手動解綁可用如下API
// 解綁objectA的全部 property
[DVDataBind unbindWithTarget:objectA];
// 解綁objectA的 a
[DVDataBind unbindWithTarget:objectA property:@"a"];
// 解綁objectA的 控件a
[DVDataBind unbindWithTarget:objectA property:@"a" controlEvent:UIControlEventValueChanged];
// 解綁objectA的 數組a 的某位
[DVDataBind unbindWithTarget:objectA property:@"a" index:1];
// 解綁objectA的 a 所在數據鏈的 輸出Block "XXX"
[DVDataBind unbindWithTarget: objectA property:@"a" outBlockKey:@"XXX"];
複製代碼

5.如何導入項目

  1. 編譯DVDataBindKitShell 網絡

  2. 生成Framework拖入項目 架構

  3. 項目 Target -> Build Settings -> Linking ->Other Linker Flags 添加參數: -all_load -ObjC 框架

  4. 在PCH文件導入ide

#import <DVDataBindKit/DVDataBindKit.h>
複製代碼

6.結語:

github地址: github.com/shidavid/DV…
謝謝你們觀看,有興趣麻煩點個星星關注下 😁😁😁post

相關文章
相關標籤/搜索