很早就开始关注ReactiveCocoa了,前段时间决定把它加入到项目中,理由如下

  • 熟悉响应式编程(函数式编程)模式的好时机
  • 整个框架经过0.0.1版本到2.3.1的迭代已经相对成熟
  • 反正我现在一个开发,不用考虑其他人看不懂的情况
  • MVVM模式的尝试

从开始了解ReactiveCocoa到现在,有时候总感觉没有完全利用好,比如

@weakify(self);
[[self.nextButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
     @strongify(self);
     [self performSegueWithIdentifier:@"Captcha" sender:nil];
}];

这样单一的信号传递和部署,没有和其他信号有任何的联系,总感觉和action-target模式没什么区别,觉得这偏离了响应式编程的本意,或许这样说有点极端。

先说说很常用的验证码倒计时功能,用RAC来实现几乎一气呵成
验证码
点击按钮可以重新开始倒计时

        @weakify(self);
    RACSignal *timeSignal = [[[[[RACSignal interval:1.0f onScheduler:[RACScheduler mainThreadScheduler]] take:numberLimit] startWith:@(1)] map:^id(NSDate *date) {
        @strongify(self);
        if (number == 0) {
            [self.timeButton setTitle:@"重新发送" forState:UIControlStateNormal];
            return @YES;
        }
        else{
            self.timeButton.titleLabel.text = [NSString stringWithFormat:@"%d", number--];
            return @NO;
        }
    }] takeUntil:self.rac_willDeallocSignal];

    self.timeButton.rac_command = [[RACCommand alloc]initWithEnabled:timeSignal signalBlock:^RACSignal *(id input) {
        number = numberLimit;
        return timeSignal;
    }];
  • 利用+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler方法返回一个在主线程上间隔一秒执行一次的signal,RAC内部是利用dispatch_source_set_timer dispatch_source_set_event_handler dispatch_resume这一系列的GCD方法来实现的。注意这个signalsendnext的值是当前日期的NSDate对象,不过这个值对于整个功能是没有用处的。
  • - (instancetype)take:(NSUInteger)count取前numberLimitsendnext的值,相当于我们需要倒计时多久
  • 通过- (instancetype)startWith:(id)value修改第一次sendnext的值,并且立即sendnext这个值。理论上对于sendnext的值是不需要处理的,原因是+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler所返回的signal会停顿1秒才执行,利用startWith来立即开始倒计时罢了,所以说startWith:的参数值是什么不重要
  • 利用- (instancetype)map:(id (^)(id value))block修改sendnext最终的值返回@YES或者@NO
  • - (RACSignal *)takeUntil:(RACSignal *)signalTrigger意思是当这个VC的即将dealloc的时候停止倒计时,RAC内部其实就是sendCompleted
  • 最后把timeSignaltimeButton.rac_command绑定判断按钮是否可用,并且启动timeSignal。按钮被点击的时候恢复number的值,return buttonSignal来重新启动倒计时timeSignal

如果RAC利用得当,几乎可以抛弃写自定义的委托协议。想象一下:当aVC presentViewController 到bVC,然后 dismissViewControllerAnimated bVC的时候需要在aVC中做一些事情,这时候通常用委托协议可以解决这种问题,比较麻烦。利用RAC实现就显得非常简洁明了

其中的关键方法是- (RACSignal *)rac_signalForSelector:(SEL)selector,基本使用是这样的

[[self rac_signalForSelector:@selector(dismiss:)] subscribeNext:^(id x) {
    NSLog(@"%s", __func__);
}];

- (IBAction)dismiss:(id)sender{
    [self dismissViewControllerAnimated:YES completion:NULL];
}

dismiss:方法被执行后信号就会被部署

信号什么时候部署可以由我们来决定,既然需要在aVC中处理一些事,那么就应该想办法在aVC中来部署信号。这时候就需要把信号作为bVC的属性

bVC.h

#import <UIKit/UIKit.h>

@interface RACDelegateViewController : UIViewController
@property (nonatomic, strong) RACSignal *delegateSignal;
@end

部署信号

aVC.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if ([segue.destinationViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController *nav = (UINavigationController *)segue.destinationViewController;
        RACDelegateViewController *delegateVC = (RACDelegateViewController *)nav.topViewController;
        [delegateVC.delegateSignal subscribeNext:^(id x) {
            NSLog(@"%@", x);
        }];
    }
}

还有最后一步,生成delegateSignal

bVC.m

- (void)awakeFromNib{
    [super awakeFromNib];
    self.delegateSignal = [self rac_signalForSelector:@selector(dismiss:)];
}

注意:千万不要在- (void)viewDidLoad;去生成delegateSignal,这会导致在执行subscribeNext的时候delegateSignal还是空的。

还不够完美对不对,aVC中接收的数据一直都是UIBarButtonItem对象,想把bVC中的数据传递到aVC中应该如何实现。还必须在信号上做一些调整,利用 - (RACSignal *)then:(RACSignal * (^)(void))block; 这个方法功能是忽略接收者所有的next,直到信号complete返回一个新的RACSignal。看用法:

- (void)awakeFromNib{
    [super awakeFromNib];
    @weakify(self);
    self.delegateSignal = [[self rac_signalForSelector:@selector(dismiss:)] then:^RACSignal *{
        @strongify(self);
        return [RACSignal return:self.array];
    }];
}

[self rac_signalForSelector:@selector(dismiss:)]信号sendComplete的时候,执行一个block,这个block必须返回一个不为空的新的RACSignal。当然还有其他方法,比如:map这个信号:

- (void)awakeFromNib{
    [super awakeFromNib];
    @weakify(self);
    self.delegateSignal = [[self rac_signalForSelector:@selector(dismiss:)] map:^id(id value) {
        @strongify(self);
        return self.array;
    }];
}

对于需要监听协议方法的时候可以使用 - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol 具体可以看我的笔记共享


前段时间看了objc中国的两篇文章更轻量的 View Controllers整洁的 Table View 代码,也试着把ReactiveCocoa结合进UITableView

  • KVO数据源数组
- (id)initWithCellIdentifier:(NSString *)aCellIdentifier
          configureCellBlock:(CellConfigureBlock)aConfigureCellBlock{
    self = [super init];
    if (self) {
        _cellIdentifier = aCellIdentifier;
        _configureCellBlock = [aConfigureCellBlock copy];
        _signal = RACObserve(self, dataSource);
    }
    return self;
}
  • map数据源得到单一的数据模型
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellIdentifier
                                                            forIndexPath:indexPath];
    if (self.configureCellBlock) {
        @weakify(self);
        self.configureCellBlock(cell, [self.signal map:^id(NSArray *array) {
            @strongify(self);
            return [self itemAtIndexPath:indexPath];
        }]);

    }
    return cell;
}
  • VC中的用法
self.model = [[DataSourceGeneralModel alloc] initWithCellIdentifier:CelIdentifier
                                                 configureCellBlock:^(TableViewCell *cell, RACSignal *signal) {
        [cell configureCellWithSignal:signal];
    }];

  • cell中配置
- (void)configureCellWithSignal:(RACSignal *)signal{
    @weakify(self);
    [signal subscribeNext:^(Model *model) {
        @strongify(self);
        self.titleLabel.text = model.title;
        self.detailLabel.text = model.detail;
    }];
}

如果更新数据源数组,UITableView也会得到相应的更新,不用调用[self.tableView reloadData],像这样更新即可

self.model.dataSource = @[model1];

以上例子可以在github下载

待续..........


RAC相关博客


bawn
515 声望44 粉丝

欢迎技术交流