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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容