iOS

ReactiveCocoa基本使用与MVVM+RAC框架搭建

Posted by lingjye on April 20, 2017

简介

RAC是函数式+响应式编程(FRP)结合,git仓库:https://github.com/ReactiveCocoa/ReactiveCocoa

它由四大核心组件构成:

  • 信号源:RACStream 及其子类;
  • 订阅者:RACSubscriber 的实现类及其子类;
  • 调度器:RACScheduler 及其子类;
  • 清洁工:RACDisposable 及其子类。

详细介绍可以前往ReactiveCocoa v2.5 源码解析之架构总览

RAC基本使用

示例:

代理

1
2
3
4
5
6
7
8
@weakify(self);
[[self rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:)
                fromProtocol:@protocol(UITableViewDelegate)] subscribeNext:^(RACTuple *tuple) {
    NSIndexPath *indexPath = tuple.last;
    @strongify(self);
    [[NSNotificationCenter defaultCenter] postNotificationName:RACTestNotification object:indexPath];
    self.selectIndex = indexPath.row;
}];

target-action

1
2
3
4
5
6
7
8
9
10
11
12
UIButton *testButton = ({
    UIButton *button = [UIButton buttonWithType:0];
    button.frame = CGRectMake(100, 50, 100, 30);
    [button setTitle:@"点击" forState:UIControlStateNormal];
    [button setBackgroundColor:UIColor.redColor];
    button;
});
[self.view addSubview:testButton];
    
[[testButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    NSLog(@"点击:%@", x);
}];

通知,内部在订阅结束后移除观察者,所以不需要手动移除观察者

1
2
3
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:RACTestNotification object:nil] subscribeNext:^(NSNotification *noti) {
    NSLog(@"%@", noti.object);
}];

KVO,其内部使用RACKVOTrampoline(RACDisposable子类)会自动维护监听者的释放,也不需要移除

1
2
3
4
// 警告fix: pod 'ReactiveCocoa', :git => 'https://github.com/zhao0/ReactiveCocoa.git', :tag => '2.5.2'
[RACObserve(self, selectIndex) subscribeNext:^(id x) {
    NSLog(@"%tu", [x integerValue]);
}];

组合

1
2
3
4
5
6
7
8
9
10
11
12
[[[RACSignal
   combineLatest:@[ RACObserve(self, selectIndex), RACObserve(self, title) ]
   reduce:^(NSNumber *selectIndex, NSString *title) {
       return @(selectIndex.integerValue > 0 && title.length);
   }] distinctUntilChanged]
 subscribeNext:^(NSNumber *valid) {
     if (valid.boolValue) {
         NSLog(@"符合条件");
     } else {
         NSLog(@"不符合条件");
     }
 }];

映射

1
2
3
4
5
6
7
8
RACSubject *subject = [RACSubject subject];
RACSignal *signal = [subject map:^id(id value) {
    return [value stringByAppendingString:@"_hello"];
}];
[signal subscribeNext:^(id x) {
    NSLog(@"%s, %@", __func__, x);
}];
[subject sendNext:@"hi"];

绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
RACSubject *subject = [RACSubject subject];
RACSignal *bindSignal = [subject bind:^RACStreamBindBlock{
    // block 绑定信号就会调用 此处一般不做处理
    return ^RACSignal *(id value, BOOL *stop) {
        // 处理一般在此处开始
        // (源信号)绑定信号发送新值时调用
        NSLog(@"接收到的内容:%@", value);
        // *stop = YES 本次订阅后终止
        if ([value isEqualToString:@"123"]) {
            *stop = YES;
        }
        // 返回信号 或者empty
        return [RACReturnSignal return:[value stringByAppendingString:@"_hello"]];
    };
}];
    
[bindSignal subscribeNext:^(id x) {
    // 改变后的值
    NSLog(@"%s, %@", __func__, x);
}];
[subject sendNext:@"abc"];
[subject sendNext:@"123"];
[subject sendNext:@"456"];

跳跃

1
2
3
4
5
6
7
8
// 跳跃过前面n个值
RACSubject *subject = [RACSubject subject];
[[subject skip:1] subscribeNext:^(id x) {
    NSLog(@"%s, %@", __func__, x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];

take

1
2
3
4
5
6
7
RACSubject *subject = [RACSubject subject];
[[subject take:2] subscribeNext:^(id x) {
    NSLog(@"%s, %@", __func__, x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];

元组

1
2
3
4
5
6
7
8
 // 包装元组
RACTuple *tuple = RACTuplePack(@1, @2, @3);
NSLog(@"%s, %@", __func__, tuple);

 // 解包
RACTuple *tuple = RACTuplePack(@1, @2);
RACTupleUnpack(NSNumber * number1, NSNumber * number2) = tuple;
NSLog(@"%s, %@, %@", __func__, number1, number2);

解决循环引用@weakify(self)和@strongify(self)

1
2
3
4
5
6
@weakify(self);
[[self rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate)] subscribeNext:^(RACTuple *tuple) {
    @strongify(self);
    NSIndexPath *indexaPath = tuple.last;
    self.selectIndex = indexaPath.row;
}];

冷信号转换为热信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 冷信号转换为热信号 适用场景:多个订阅者订阅一个信号, 不需要发送多个消息
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    // 不管有几个订阅者, 只发送一次
    NSLog(@"发送消息");
    [subscriber sendNext:@"abc"];
    [subscriber sendCompleted];
    return nil;
}];
// 创建连接类
RACMulticastConnection *connection = [signal publish];
[connection.signal subscribeNext:^(id x) {
    NSLog(@"订阅者1: %@", x);
}];
[connection.signal subscribeNext:^(id x) {
    NSLog(@"订阅者2: %@", x);
}];
[connection.signal subscribeNext:^(id x) {
    NSLog(@"订阅者3: %@", x);
}];
// 创建连接, 此过程将会把冷信号转化为热信号
[connection connect];

RACSignal用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"abc"];
    // 必须调用 或者调用sendError
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{
        // 信号取消订阅时调用
        NSLog(@"%s, 订阅取消", __func__);
    }];
}];
// 订阅后返回取消订阅信号
RACDisposable *disposable = [signal subscribeNext:^(id x) {
    NSLog(@"%s, %@", __func__, x);
}];
// 取消订阅
[disposable dispose];

RACSubject用法

1
2
3
4
5
6
7
8
9
RACSubject *subject = [RACSubject subject];
// 必须先订阅, 才能发送
[subject sendNext:@"abc"];
// 指明生命周期
[[subject takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
    NSLog(@"%s, %@", __func__, x);
}];

[subject sendNext:@"123"];

RACCommand用法

1
2
3
4
5
6
7
// RACCommand 
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        return dispose;
    }];
}];
[command execute:nil];

RACSequence用法

1
2
3
4
5
6
7
8
// RACSequence
NSArray *strings = @[ @"as", @"safaf", @"sfasdfds", @"dfdsg" ];
NSArray *results = [[[strings.rac_sequence filter:^BOOL(NSString *str) {
    return str.length > 2;
}] map:^id(NSString *str) {
    return [str stringByAppendingString:@"_hello"];
}] array];
NSLog(@"%@", results);

MVVM

MVVM由Microsoft架构师Ken Cooper和Ted Peters提出,用于简化用户界面的事件驱动编程。

在MVC中大致是这样的:

MVC

在MVVM是这样的:

MVVM

使用MVVM的优点:

  1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变;
  2. 可重用性。可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑;
  3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面(View)设计,使用Expression Blend可以很容易设计界面并生成xaml代码;
  4. 可测试。针对ViewModel来对View写测试代码,会简单些,它能够减少Controller的复杂性,使得表示逻辑更易测试。

更多用法请查看 Demo

MVVM+RAC