RAC 雙向綁定實(shí)現(xiàn)案例

案例1:正常情況下實(shí)現(xiàn)兩個(gè)屬性雙向綁定


方法一:

RACChannelTo(view, property) = RACChannelTo(model, property);

方法二:(與方法一完全等價(jià))

[[RACKVOChannel alloc] initWithTarget:view keyPath:@"property" nilValue:nil][@"followingTerminal"] 
= [[RACKVOChannel alloc] initWithTarget:model keyPath:@"property" nilValue:nil][@"followingTerminal"];

方法三:(中間需要做一些映射轉(zhuǎn)換的)

RACChannelTerminal *channelA = RACChannelTo(self, valueA);
RACChannelTerminal *channelB = RACChannelTo(self, valueB);

// valueA: On表示打開(kāi),Off表示關(guān)閉
// valueB: 1表示打開(kāi)场斑,0表示關(guān)閉

[[channelA map:^id(NSString *value) {
    if ([value isEqualToString:@"On"]) {
        return @"1";
    } else {
        return @"0";
    }
}] subscribe:channelB];

[[channelB map:^id(NSString *value) {
    if ([value isEqualToString:@"1"]) {
        return @"On";
    } else {
        return @"Off";
    }
}] subscribe:channelA];

案例2:實(shí)現(xiàn)UISwitch跟隨NSUserDefaults存儲(chǔ)的值變化


方法1:

[[RACKVOChannel alloc] initWithTarget:[NSUserDefaults standardUserDefaults]
                                  keyPath:@"someBoolKey" nilValue:@(NO)][@"followingTerminal"] = [[RACKVOChannel alloc] initWithTarget:self.someSwitch keyPath:@"on" nilValue:@(NO)][@"followingTerminal"];
    
// 上面的不能完全實(shí)現(xiàn)雙向綁定扣猫,因?yàn)?UISwitch 的 on 屬性是不支持 KVO 的                        
@weakify(self)
[self.someSwitch.rac_newOnChannel subscribeNext:^(NSNumber *onValue) {
    @strongify(self)
    
    // 下面兩句都可以
    [self.someSwitch setValue:onValue forKey:@"on"];
    //[[NSUserDefaults standardUserDefaults] setObject:onValue forKey:@"someBoolKey"];
}];

方法2:代碼摘自stackoverflow上一個(gè)問(wèn)題答案

// 注意下面代碼實(shí)現(xiàn)是不能滿足的
RACChannelTerminal *switchTerminal = self.someSwitch.rac_newOnChannel;
RACChannelTerminal *defaultsTerminal = [[NSUserDefaults standardUserDefaults] rac_channelTerminalForKey:@"someBoolKey"];
[switchTerminal subscribe:defaultsTerminal];
[defaultsTerminal subscribe:switchTerminal];

但是我自己創(chuàng)建了一個(gè)工程發(fā)現(xiàn)這個(gè)雙向綁定有問(wèn)題卡骂,我點(diǎn)擊兩次UISwitch后钞楼,再用代碼修改NSUserDefaults中對(duì)應(yīng)值,結(jié)果UISwitch沒(méi)有變化振湾。

經(jīng)過(guò)調(diào)試發(fā)現(xiàn)原因是因?yàn)楫?dāng)操作UISwitch控件時(shí)暇务,觸發(fā)defaultsTerminal,但是RAC的NSUserDefaults+RACSupport的rac_channelTerminalForKey實(shí)現(xiàn)中filter操作會(huì)過(guò)濾拿撩,導(dǎo)致后面的distinctUntilChanged操作中的__block變量lastValue沒(méi)有更新衣厘,這樣下次再修改NSUserDefaults中的相應(yīng)值時(shí),distinctUntilChanged對(duì)比的已經(jīng)是上上次的lastValue压恒,導(dǎo)致defaultsTerminal沒(méi)有觸發(fā)影暴,從而沒(méi)有觸發(fā)switchTerminal,從而導(dǎo)致雙向綁定失敗探赫。

我暫時(shí)的解決方法是新建一個(gè)NSUserDefaults+CustomRACSupport的category方法型宙,將原先實(shí)現(xiàn)中的"filter"操作去掉,因?yàn)?distinctUntilChanged"已經(jīng)能做"filter"操作想做的事情伦吠。

去掉"filter"操作后的方法實(shí)現(xiàn)如下:

- (RACChannelTerminal *)customChannelTerminalForKey:(NSString *)key {
    RACChannel *channel = [RACChannel new];
    
    RACScheduler *scheduler = [RACScheduler scheduler];
    
    @weakify(self);
    [[[[[[NSNotificationCenter.defaultCenter
        rac_addObserverForName:NSUserDefaultsDidChangeNotification object:self]
        map:^(id _) {
            @strongify(self);
            return [self objectForKey:key];
        }]
        startWith:[self objectForKey:key]]
        distinctUntilChanged]
        takeUntil:self.rac_willDeallocSignal]
        subscribe:channel.leadingTerminal];
    
    [[channel.leadingTerminal
        deliverOn:scheduler]
        subscribeNext:^(id value) {
            @strongify(self);
            [self setObject:value forKey:key];
        }];
    
    return channel.followingTerminal;
}

案例3:UITextField/UITextView與viewModel中的text屬性雙向綁定


如果將textView的數(shù)據(jù)綁定寫(xiě)成下面這樣

RACChannelTo(self, uiTextView.text) = RACChannelTo(self, viewModel.text);

你會(huì)發(fā)現(xiàn)viewModel.text不會(huì)隨著鍵盤(pán)輸入的內(nèi)容改變而發(fā)生變化妆兑。但是用代碼修改viewModel.text的值時(shí)代碼改變的值卻能同步到uiTextView上面。

具體原因可以查看stackoverflow上一個(gè)相似的issue

其實(shí)官方文檔是這么說(shuō)的:UIKit classes don't expose KVO-compliant properties UIKIt里面的很多控件本身不支持KVO毛仪,而ReactiveCocoa本身是基于KVO實(shí)現(xiàn)的搁嗓,所以就會(huì)出現(xiàn)這種雙向綁定不成功的現(xiàn)象,這時(shí)候就需要我們手動(dòng)用信號(hào)箱靴,或者是rac提供的其他屬性來(lái)做處理完成雙向綁定的操作

另外注意下 self.uiTextField.rac_newTextChannel 與 RACChannelTo(self.uiTextField, text) 的區(qū)別
同樣的 self.uiTextView/uiTextField.rac_textSignal 與 RACObserve(self.uiTextView/uiTextField, text)也有該區(qū)別

self.uiTextField.rac_newTextChannel sends values when you type in the text field, but not when you change the text in the text field from code.

RACChannelTo(self.uiTextField, text) sends values when you change the text in the text field from code, but not when you type in the text field.

所以代碼寫(xiě)成下面這樣也是有漏洞的:

RACChannelTerminal *textFieldChannelT = uiTextField.rac_newTextChannel;
RAC(self.viewModel, text) = textFieldChannelT;
[RACObserve(self.viewModel, text) subscribe:textFieldChannelT];
// 當(dāng)用代碼給uiTextField.text賦值時(shí)會(huì)影響不到self.viewModel.text

順便提一個(gè)自己曾經(jīng)遇到的坑:
當(dāng)訂閱self.uiTextView.rac_textSignal后腺逛,原先uiTextView設(shè)置的delegate相關(guān)委托方法會(huì)不回調(diào)。(UITextField沒(méi)有這個(gè)問(wèn)題衡怀,具體原因可以看下ReactiveCocoa的UITextView的rac_textSignal的實(shí)現(xiàn))

解決方法:

由于使用代碼對(duì)model到view這個(gè)方向的綁定是沒(méi)問(wèn)題的棍矛,所以我們只要在textView的text改變的信號(hào)中做一個(gè)手動(dòng)的設(shè)置值(在subscribeNext中主動(dòng)設(shè)置model對(duì)應(yīng)的屬性值就可以完成雙向綁定了)

代碼如下:

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
 
@interface Model : NSObject

@property (nonatomic, strong) NSString *text;

@end

@implementation Model

@end


@interface ViewController ()

@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, strong) Model *model;
@property (nonatomic, copy) NSString *str;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 80, CGRectGetWidth(self.view.bounds), 300)];
    self.textView.backgroundColor = [UIColor redColor];
    [self.view addSubview:self.textView];

    self.model = [Model new];

    // 這種寫(xiě)法其實(shí)已經(jīng)是雙向綁定的寫(xiě)法了,但是由于是textView的原因只能綁定model.text的變化到影響textView.text的值的變化的這個(gè)單向通道
    RACChannelTo(self,textView.text) = RACChannelTo(self,model.text);

    // 在這里對(duì)textView的text changed的信號(hào)重新訂閱一下狈癞,以實(shí)現(xiàn)上面channel未實(shí)現(xiàn)的另外一個(gè)綁定通道.
   @weakify(self)
   [self.textView.rac_textSignal subscribeNext:^(id x) {
       @strongify(self)

       self.model.text = x;
       NSLog(@"model text is%@",self.model.text);
   }];

   UIButton *resetBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 480, 60, 40)];
   resetBtn.backgroundColor = [UIColor yellowColor];
   [resetBtn setTitle:@"reset" forState:UIControlStateNormal];
   [self.view addSubview:resetBtn];

   resetBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        @strongify(self)
        RACSignal *signal = [RACSignal return:input];
        [signal subscribeNext:^(id x) {
            self.model.text = @"reset yet";
            NSLog(@"model text is%@",self.model.text);
        }];

        return signal;
    }];
}

@end

還有兩個(gè)有趣的案例 詳見(jiàn)鏈接

本文代碼詳見(jiàn):https://github.com/BenXia/RACTwoWayBinding

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茄靠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蝶桶,更是在濱河造成了極大的恐慌慨绳,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脐雪,居然都是意外死亡厌小,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門(mén)战秋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)璧亚,“玉大人,你說(shuō)我怎么就攤上這事脂信⊙Ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵狰闪,是天一觀的道長(zhǎng)疯搅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)埋泵,這世上最難降的妖魔是什么幔欧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮丽声,結(jié)果婚禮上礁蔗,老公的妹妹穿的比我還像新娘。我一直安慰自己雁社,他們只是感情好浴井,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著歧胁,像睡著了一般滋饲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喊巍,一...
    開(kāi)封第一講書(shū)人閱讀 52,821評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音箍鼓,去河邊找鬼崭参。 笑死,一個(gè)胖子當(dāng)著我的面吹牛款咖,可吹牛的內(nèi)容都是我干的何暮。 我是一名探鬼主播,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铐殃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼海洼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起富腊,我...
    開(kāi)封第一講書(shū)人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坏逢,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體是整,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肖揣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浮入。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龙优。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖事秀,靈堂內(nèi)的尸體忽然破棺而出彤断,到底是詐尸還是另有隱情,我是刑警寧澤易迹,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布宰衙,位于F島的核電站,受9級(jí)特大地震影響赴蝇,放射性物質(zhì)發(fā)生泄漏菩浙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一句伶、第九天 我趴在偏房一處隱蔽的房頂上張望劲蜻。 院中可真熱鬧,春花似錦考余、人聲如沸先嬉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疫蔓。三九已至,卻和暖如春身冬,著一層夾襖步出監(jiān)牢的瞬間衅胀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工酥筝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滚躯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓嘿歌,卻偏偏與公主長(zhǎng)得像掸掏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宙帝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容