美文网首页
RAC中双向监听回声问题

RAC中双向监听回声问题

作者: NSBug | 来源:发表于2018-08-09 22:45 被阅读96次

前言

公司产品最近要实现一个需求,大概如下:
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版本。

相关文章

  • RAC中双向监听回声问题

    前言 公司产品最近要实现一个需求,大概如下:1.一个UITextFidle和UISwitch联动 2.UIText...

  • iOS 监听键盘事件

    Swift RAC 监听 系统方法监听 响应方法 remove observer Objective-C RAC 监听

  • RAC的常见应用场景

    这里写RAC常见的应用场景 RAC集合 代替KVO 监听事件 代替通知 监听文本框5.代理 RAC集合 RACTu...

  • ReactiveCocoa(RAC) 2018-01-25

    RAC监听按钮点击事件 [[self.button rac_signalForControlEvents:UICo...

  • RAC iOS

    使用RAC 1.target-action RAC最基本的入门使用技巧就是对事件的监听。 PS:在iOS开发中,我...

  • RAC给UITextField添加代理回调

    使用RAC监听UITextField的文本可以用rac_textSignal,但是rac_textSignal是实...

  • ReactiveObjC使用

    一.RAC基础使用 监听方法调用 KVO 通知 监听View事件 监听Timer RACSignal RACSig...

  • RAC(ReactiveCocoa)小记-新人必看

    一、配置RAC环境 : 二、RAC能干什么? 1、对事件的监听 例如我们监听一个textfield输入完成的事件,...

  • RAC使用

    RAC常用宏 KVO监听使用 RAC宏使用 信号类使用 使用信号模拟代理 rac_sequence遍历字典 解包元...

  • ReactiveObjC 监听通知遇到的坑

    RAC 功能很多,也很强大,但是在使用监听通知的时候,遇到一个问题,就是监听不能被释放: 这样写虽然方便。但是,不...

网友评论

      本文标题:RAC中双向监听回声问题

      本文链接:https://www.haomeiwen.com/subject/tgfhbftx.html