RAC中双向监听回声问题

前言

公司产品最近要实现一个需求,大概如下:
1.一个UITextFidleUISwitch联动

2.UITextFidle在编辑状态下,UISwitch处于开启状态

3.UISwitch处于开启状态时,UITextFidle相应的处于可编辑状态

  1. UITextFidle退出编辑状态时,输入框未输入任何字符,UISwitch处于关闭状态

  2. UISwitch处于关闭状态时,UITextFidle退出编辑状态

  3. UITextFidle退出编辑状态时,输入框有字符,UISwitch处于开启状态

看到这样的需求,第一时间想到利用RAC监听属性变化去实现。

回声问题

回声问题指的是,当相互监听属性时,不仅对方可以监听到自己属性的变化,自己也可以监听到自己的变化。这样就陷入了一个死循环,两边都能听到对方的变化,还能同时听到自己的变化。

例如这样去实现:

    RACSignal *editingDidBeginSignal = [[textField rac_signalForControlEvents:UIControlEventEditingDidBegin] mapReplace:@1];
    RACSignal *editingDidEndSignal = [[textField rac_signalForControlEvents:UIControlEventEditingDidEnd] map:^id(UITextField *value) {
        return value.text.length ? @1 : @0;
    }];
    RACSignal *switchSignal = [[valueSwitch rac_signalForControlEvents:UIControlEventTouchUpInside] map:^NSNumber *(UISwitch *value) {
        return @(value.on);
    }];
    
    [[editingDidEndSignal merge:editingDidBeginSignal] subscribeNext:^(NSNumber *x) {
        valueSwitch.on = [x boolValue];
    }];
    
    [switchSignal subscribeNext:^(NSNumber *x) {
        x.boolValue ? [textField becomeFirstResponder] : [textField resignFirstResponder];
    }];

这就会产生典型的回声问题,当UITextFidle编辑状态改变时,会改变UISwitch的开闭状态;而UISwitch的开闭状态改变时,UITextFidle又会监听到变化改变编辑状态,从而进入了无限的循环之中。

RACChannel

RAC中是有实现双向绑定的成熟方案的,这就是RACChannel与RACChannelTerminal。例如两个UITextFidle,任意一个UITextFidle输入文本变化时,另一个也要跟着变化

代码很简单,RAC给UITextFidle添加了分类方法- (RACChannelTerminal *)rac_newTextChannel;,可以很简单的去生成RACChannelTerminal,去实现双向绑定。同样也给UITextViewUISwitchUIStepperUISliderUISegmentedControlUIControlUIDatePicker控件添加了相应的分类方法去生成RACChannelTerminal。
上图中的代码也很简单:

    [textField1.rac_newTextChannel subscribe:textField2.rac_newTextChannel];
    [textField2.rac_newTextChannel subscribe:textField1.rac_newTextChannel];
什么是```RACChannel和RACChannelTerminal呢?

RACChannel可以看成是一个双向通道,由两个并行工作的可控信号组成。而RACChannelTerminal则是这个双向通道的一端。可以简单理解为,两个属性双向监听,相当于在这两个属性直接建立个一个通道,而其中的一端就是RACChannelTerminal。类比于网络编程里面socket的概念,RACChannel类似网络链接通道,RACChannelTerminal类似于socket。
具体实现可以参考这篇文章

关于上面的需求

本来我是想利用RACChannelTerminal去实现的,可以看到UITextField的分类实现

- (RACChannelTerminal *)rac_newTextChannel {
    return [self rac_channelForControlEvents:UIControlEventAllEditingEvents key:@keypath(self.text) nilValue:@""];
}
- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue {
    NSCParameterAssert(key.length > 0);
    key = [key copy];
    RACChannel *channel = [[RACChannel alloc] init];

    [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
        [channel.followingTerminal sendCompleted];
    }]];

    RACSignal *eventSignal = [[[self
        rac_signalForControlEvents:controlEvents]
        mapReplace:key]
        takeUntil:[[channel.followingTerminal
            ignoreValues]
            catchTo:RACSignal.empty]];
    [[self
        rac_liftSelector:@selector(valueForKey:) withSignals:eventSignal, nil]
        subscribe:channel.followingTerminal];

    RACSignal *valuesSignal = [channel.followingTerminal
        map:^(id value) {
            return value ?: nilValue;
        }];
    [self rac_liftSelector:@selector(setValue:forKey:) withSignals:valuesSignal, [RACSignal return:key], nil];

    return channel.leadingTerminal;
}

是监听text属性的变化,想仿照此写法但是发现UITextField成为第一响应者和退出第一响应者,没法找到具体的属性,这里也就没法利用RACChannelTerminal去实现了。

转化下思路,既然没法用现成的方案实现,可以参考RACChannel的实现思路,自己去实现双向绑定。

    RACSignal *editingDidBeginSignal = [[textField rac_signalForControlEvents:UIControlEventEditingDidBegin] mapReplace:@1];
    RACSignal *editingDidEndSignal = [[textField rac_signalForControlEvents:UIControlEventEditingDidEnd] map:^id(UITextField *value) {
        return value.text.length ? @1 : @0;
    }];
    
    //转化成热信号
    RACSubject *textEditingSignal = (RACSubject *)[[editingDidBeginSignal merge:editingDidEndSignal] replay];
    RACSubject *switchSignal = (RACSubject *)[[[valueSwitch rac_signalForControlEvents:UIControlEventTouchUpInside] map:^NSNumber *(UISwitch *value) {
        return @(value.on);
    }] replay];
    
    //ignoreValues避免自己可以监听到自己的变化,处理回声问题的关键
    //subscribe:方法使后者成为前者的其中之一的订阅者
    [[textEditingSignal ignoreValues] subscribe:switchSignal];
    [[switchSignal ignoreValues] subscribe:textEditingSignal];
    
    //订阅UITextField和UISwitch相应的信号
    [textEditingSignal subscribeNext:^(id x) {
        valueSwitch.on = [x boolValue];
    }];
    [switchSignal subscribeNext:^(id x) {
        if ([x boolValue]) {
            //选中
            [textField becomeFirstResponder];
        } else {
            //未选中
            textField.text = @"";
            [textField resignFirstResponder];
        }
    }];

具体思路:
1.分别将UIControlEventEditingDidBeginUIControlEventEditingDidEnd事件产生的信号mapReplace成1和0,然后merge成一个UITextField编辑状态改变的信号;将该信号转换成热信号。
2.将UISwitch开闭状态改变的信号装换成热信号。
3.将上诉两个热信号先调取ignoreValues,这是去除回声问题的关键,忽略的所有信号中的值,使得自己无法监听到自己值得变化,打破了闭环。
4.分别调用subscribe:方法,使另一个信号成为自己的订阅者。使得对方信号发送时,自己可以监听到对方的改变。
5.分别订阅1和2中产生的信号,当其中有一个控件状态改变时,改变另一个控件的状态。这样就可以实现上面的需求了。

说明

文章中使用的RAC为2.5.0版本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351