案例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