ReactiveObjC中的多线程与绑定

本篇主要介绍ReactiveObjC中的多线程与绑定,顺便总结一下常用的宏定义与Cagegory。

按照惯例,先来一张图镇帖。

多线程

RACScheduler是RAC中和多线程相关的类,很多中文博客将其翻译成“调度器”,总之就是为了控制一个任务何时何地以何种方式被执行。RACScheduler内部并没有用到NSOperationQueue和NSRunloop等技术,而仅仅是对GCD中串行队列的简单封装,所以任务会按顺序执行,并且通过RACDisposable又支持取消。

可以简单的理解成一个RACScheduler的实例代表一个GCD中的队列。

获取/创建

几个主要的获取/创建队列的类方法:

+ (RACScheduler *)immediateScheduler;
+ (RACScheduler *)mainThreadScheduler;
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
+ (RACScheduler *)scheduler;
+ (RACScheduler *)currentScheduler;

顾名思义,看一眼就能知道是什么意思,不解释了。其中,RACSchedulerPriority是一个枚举值,定义如下

typedef enum : long {
    RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
    RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
    RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
    RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;

可见,就是和GCD中的四个队列优先级一毛一样。

执行

几个执行任务的实例方法:

- (RACDisposable *)schedule:(void (^)(void))block;
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block;
- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block;

其中第一个是立即执行,后两个是延迟执行,也很简单,不多解释了。

下面重点介绍一下和RACScheduler相关的几个操作符。

何地

控制代码在何地(哪个线程中)执行。

- (RACSignal *)deliverOn:(RACScheduler *)scheduler;
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler;

为了区分这两个操作符,首先定义一个辅助方法:

- (RACSignal *)bgThreadSendEventSignal {
    NSLog(@"Create signal in thread : %@",[NSThread currentThread]);
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] schedule:^{
            NSLog(@"Emit data in thread : %@",[NSThread currentThread]);
            [subscriber sendNext:@"🍎"];
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}

未应用这两个操作符的情况下:

NSLog(@"Subscribe signal in thread : %@",[NSThread currentThread]);
[[[[self bgThreadSendEventSignal] doNext:^(id x) {
   NSLog(@"Intercept %@ in thread : %@",x,[NSThread currentThread]);
}] map:^id(id value) {
   NSLog(@"Map %@ in thread : %@",value,[NSThread currentThread]);
   return @"🍉";
}] subscribeNext:^(id x) {
   NSLog(@"Receive %@ in thread : %@",x,[NSThread currentThread]);
}];

结果是:

Subscribe signal in thread : <NSThread: 0x600000066c00>{number = 1, name = main}
Create signal in thread : <NSThread: 0x600000066c00>{number = 1, name = main}
Emit data in thread : <NSThread: 0x60000046bcc0>{number = 3, name = (null)}
Intercept 🍎 in thread : <NSThread: 0x60000046bcc0>{number = 3, name = (null)}
Map 🍎 in thread : <NSThread: 0x60000046bcc0>{number = 3, name = (null)}
Receive 🍉 in thread : <NSThread: 0x60000046bcc0>{number = 3, name = (null)}    

观察结果可以发现:

一个信号被创建和订阅之后事件(数据)流经历了四个过程:发送-拦截-转换-接收;而它们都发生在同一个线程中,也就是最初发送事件所在的线程。

deliverOn

看代码:

NSLog(@"Subscribe signal in thread : %@",[NSThread currentThread]);
[[[[[[self bgThreadSendEventSignal] deliverOn:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground]] doNext:^(id x) {
    NSLog(@"Intercept %@ in thread : %@",x,[NSThread currentThread]);
}] map:^id(id value) {
    NSLog(@"Map %@ in thread : %@",value,[NSThread currentThread]);
    return @"🍉";
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
    NSLog(@"Receive %@ in thread : %@",x,[NSThread currentThread]);
}];

结果是:

Subscribe signal in thread : <NSThread: 0x6040000628c0>{number = 1, name = main}
Create signal in thread : <NSThread: 0x6040000628c0>{number = 1, name = main}
Emit data in thread : <NSThread: 0x60000007e500>{number = 3, name = (null)}
Intercept 🍎 in thread : <NSThread: 0x600000261600>{number = 4, name = (null)}
Map 🍎 in thread : <NSThread: 0x600000261600>{number = 4, name = (null)}
Receive 🍉 in thread : <NSThread: 0x6040000628c0>{number = 1, name = main}

观察结果可以发现:

deliverOn操作符可以改变之后事件流传递所在的线程;多次使用均有效。

subscribeOn

看代码:

NSLog(@"Subscribe signal in thread : %@",[NSThread currentThread]);
[[[[[[self bgThreadSendEventSignal] subscribeOn:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground]] doNext:^(id x) {
   NSLog(@"Intercept %@ in thread : %@",x,[NSThread currentThread]);
}] map:^id(id value) {
   NSLog(@"Map %@ in thread : %@",value,[NSThread currentThread]);
   return @"🍉";
}] subscribeOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
   NSLog(@"Receive %@ in thread : %@",x,[NSThread currentThread]);
}];

结果是:

Subscribe signal in thread : <NSThread: 0x60000006bc40>{number = 1, name = main}
Create signal in thread : <NSThread: 0x60000006bc40>{number = 1, name = main}
Emit data in thread : <NSThread: 0x600000462bc0>{number = 4, name = (null)}
Intercept 🍎 in thread : <NSThread: 0x600000462bc0>{number = 4, name = (null)}
Map 🍎 in thread : <NSThread: 0x600000462bc0>{number = 4, name = (null)}
Receive 🍉 in thread : <NSThread: 0x600000462bc0>{number = 4, name = (null)}

观察结果可以发现:

subscribeOn操作符可以从源头改变事件流传递所在的线程;仅第一次使用有效。

总结:deliverOnsubscribeOn更加常用,并且当然了两者可混合使用。

顺便说一下:deliverOn:[RACScheduler mainThreadScheduler]还可以用deliverOnMainThread这个操作符代替。

何时

控制代码在何时执行。

- (RACSignal *)delay:(NSTimeInterval)interval;
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;

非常简单,看例子就能懂。

delay

看代码:

NSLog(@"Create signal");
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   NSLog(@"Emit data");
   [subscriber sendNext:@"🍎"];
   [subscriber sendNext:@"🍐"];
   [subscriber sendCompleted];
   return nil;
}] delay:2.0] subscribeNext:^(id x) {
   NSLog(@"Receive data: %@",x);
}];

结果是:

0.0 Create signal
0.0 Emit data
2.0 Receive data: 🍎
2.0 Receive data: 🍐

bufferWithTime: onScheduler:

看代码:

NSLog(@"Create signal");
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   NSLog(@"Emit data");
   [subscriber sendNext:@"🍎"];
   [subscriber sendNext:@"🍐"];
   return nil;
}] bufferWithTime:2.0 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
   RACTupleUnpack(NSString *value1, NSString *value2) = (RACTuple *)x;
   NSLog(@"Receive data: %@,%@",value1,value2);
}];

结果是:

0.0 Create signal
0.0 Emit data
2.0 Receive data: 🍎,🍐

timeout: onScheduler:

看代码:

NSLog(@"Create signal");
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   NSLog(@"Emit data");
   [subscriber sendNext:@"🍎"];
   return nil;
}] timeout:2.0 onScheduler:[RACScheduler scheduler]] subscribeNext:^(id x) {
   NSLog(@"Receive data: %@",x);
} error:^(NSError *error) {
   NSLog(@"Receive error: %@ in thread: %@",error,[NSThread currentThread]);
}];

结果是:

0.0 Create signal
0.0 Emit data
0.0 Receive data: 🍎
2.0 Receive error: Error Domain=RACSignalErrorDomain Code=1 "(null)" in thread: <NSThread: 0x604000463c80>{number = 3, name = (null)}

interval: onScheduler:

看代码:

NSLog(@"Create signal");
[[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
   NSLog(@"Receive data: %@",x);
}];

结果是:

0.0 Create signal
1.0 Receive data: Wed Nov  8 20:33:07 2017
2.0 Receive data: Wed Nov  8 20:33:08 2017
3.0 Receive data: Wed Nov  8 20:33:09 2017
...

绑定

Duang!划重点!这是整个RAC库甚至响应式编程中最重要的概念,没有之一。RAC虽然号称是函数响应式编程库,但是从名称(无论是全称ReactiveCocoa还是仅针对OC版本的ReactiveObjC)上看貌似更加强调的是响应式编程。所以学习RAC库之后如果只记住一个概念,那就是–“绑定”。

单向绑定

单向绑定是指将一个对象的属性值绑定到另一个对象的属性值上。

举个🌰:将ObjA的propertyA绑定到ObjB的propertyB后,propertyB的值在任意时刻都会等于propertyA的值。当propertyA的值改变时,无需对propertyB重新赋值。

示意图如下:

在介绍如何进行单向绑定之前首先介绍另一个常用的宏定义:

RACObserve(TARGET, KEYPATH)

用于监听某个对象的某个属性的变化,返回值是一个信号。可以代替原生的KVO,一如之前展示过的。使用方法非常简单:

[RACObserve(self.userModel, starCount) subscribeNext:^(id x) { 
    // do something
}]; 

注意两点:

1 RACObserve的内部实现是基于KVO的,所以并不是所有的property都可以被RACObserve,可以的前提是该property必须支持KVO。

2 如果监听的TARGET涉及到self,同时block内部也使用了self的话,需要使用@weakify(self)@strongify(self)两个宏定义来避免循环引用:

@weakify(self);
[RACObserve(self.userModel, starCount) subscribeNext:^(id x) { 
    @strongify(self);
    self.starLabel.text = (NSString *)x;
}];

言归正传,进行单向绑定非常简单,也是一个宏定义:

RAC(TARGET, [KEYPATH, [NIL_VALUE]])

使用时将其放到 “=” 左边,右边是一个信号:

RAC(self.label, text) = self.textField.rac_textSignal;

此时,label中的文字就会随着用户在文本框中的输入而自动改变。

当然了,还可以在赋值前将原值进行转换或者过滤等等,一如之前展示过的:

RAC(self.usernameTextField, backgroundColor) = [usernameValidSignal map:^id(NSNumber *valid) {
    return [valid boolValue] ? [UIColor clearColor] : [UIColor yellowColor];
}];

前方高能!还可以结合上面的RACObserve

RAC(self.starLabel, text) = RACObserve(self.userModel, starCount);    

简直炫酷有木有?!

现在考虑这么一种情况,一个用户信息模型类当中有两个属性,分别是代表中文性别的chineseSex和英文性别的englishSex,中文性别可取值:男、女、未知,英文性别可取值:male、female、unknown。
现在要求两者时刻保持匹配。也就是当chineseSex属性变化时,englishSex要做出相应的改变,反过来亦然。

应用上面的绑定知识很容易写出来类似下面的代码:

RAC(self.englishSexLabel, text) = RACObserve(self.userModel, englishSex);
RAC(self.chineseSexLabel, text) = RACObserve(self.userModel, chineseSex);

RAC(self.userModel, chineseSex) = [RACObserve(self.userModel, englishSex) map:^id(NSString *value) {
   NSLog(@"here set chineseSex value happen once");
   if ([value isEqualToString:@"male"]) {
       return @"男";
   }
   else if ([value isEqualToString:@"female"]) {
       return @"女";
   }
   else {
       return @"未知";
   }
}];

RAC(self.userModel, englishSex) = [RACObserve(self.userModel, chineseSex) map:^id(NSString *value) {
   NSLog(@"here set englishSex value happen once");
   if ([value isEqualToString:@"男"]) {
       return @"male";
   }
   else if ([value isEqualToString:@"女"]) {
       return @"female";
   }
   else {
       return @"unknown";
   }
}];

其中前两行只是为了直观的展示两个属性值,下面的才是绑定相关。实际运行你会发现,出现循环而导致崩溃了。怎么解决呢?这就引出来下面要介绍的–双向绑定。

双向绑定

双向绑定是指将一个对象的属性值与另一个对象的属性值互相绑定。区别于单向绑定中数据的单向流动,此时数据会双向流动。

再举个🌰:将ObjA的propertyA与ObjB的propertyB互相绑定后,propertyA的值与propertyB的值在任意时刻都相等。当propertyA的值改变时,无需对propertyB重新赋值,同样,当propertyB的值改变时,也无需对propertyA重新赋值。同时还不会引发循环。

示意图如下:

可以理解,双向绑定比单向绑定功能更加强大,所以使用起来也相对困难很多。

RACChannel

怎么做呢?通过另一个类–RACChannel。

通俗的说,就像是在需要进行绑定的两个对象的属性值之间搭建一个通道,通道上有两条方向相反互不干扰的管线,从而也就实现了数据可以在两个方向的流动。

示意图如下:

RACChannelTerminal

看一下RACChannel类的头文件,发现异常简单,只有两个属性:

@property (nonatomic, strong, readonly) RACChannelTerminal *leadingTerminal;
@property (nonatomic, strong, readonly) RACChannelTerminal *followingTerminal;

所以,其实内部是靠这两个“终端”实现的。示意图如下:

再看一下RACChannelTerminal:

RACChannelTerminal : RACSignal <RACSubscriber>

可以发现,这其实也是一个信号,并且实现了RACSubscriber协议。和RACSubject有相似之处。

你一定以为接下来我要介绍如何使用RACChannel了,那你可就错了。因为通常情况下不直接用它,而是用它的一个子类–RACKVOChannel。

RACKVOChannel

那如何使用RACKVOChannel呢?看头文件只有一个初始化方法:

- (id)initWithTarget:(__weak NSObject *)target keyPath:(NSString *)keyPath nilValue:(id)nilValue;

通常也不直接使用这个方法,而是用另外一个宏定义:

RACChannelTo(TARGET, KEYPATH, NILVALUE)

传入相应的值会返回一个RACChannelTerminal类型的实例对象,同时内部会实现对相应对象属性值的监听值变化与赋新值。

示意图如下:

双向绑定的两种方法

(宏定义)直接赋值法

在对两个支持KVO的属性值进行原值(不需要中途进行转换)绑定时,一个“=”就搞定。

举个非常简单的例子:假设有两个View,redView&greenView,其中redViewalpha初始值为0.5而greenViewalpha初始值为1.0,现在要绑定它们的alpha值,就是一行代码的事:

RACChannelTo(self.redView, alpha) = RACChannelTo(self.greenView, alpha);

运行初始状态如下图:

将“=”左右的内容对调:

RACChannelTo(self.greenView, alpha) = RACChannelTo(self.redView, alpha);

运行初始状态如下图:

对比可以发现,“=”左边的属性值会从右边的属性值取得初始值。所以,使用哪个属性的初始值哪个属性就在“=”的右边。比如,将一个View和一个Model进行双向绑定时,通常都是View在左边,Model在右边。之后双方就平等了,每当一方被赋新值另一方都会自动改变,保持“同调”。

(终端)双订阅法

直接赋值法虽然足够简单,但是仅适合一小部分情况,更多的还是应用–(终端)双订阅法。

回到上面提到的需要保持代表中文性别的chineseSex属性和英文性别的englishSex属性时刻匹配的例子,可以解决如下:

// 1
RAC(self.englishSexLabel, text) = RACObserve(self.userModel, englishSex);
RAC(self.chineseSexLabel, text) = RACObserve(self.userModel, chineseSex);

// 2
RACChannelTerminal *channelEnglish = RACChannelTo(self.userModel, englishSex);
RACChannelTerminal *channelChinese = RACChannelTo(self.userModel, chineseSex);

// 3
[[chineseChannelT map:^id(NSString *value) {
    NSLog(@"here set englishSex happen once");
    if ([value isEqualToString:@"男"]) {
        return @"male";
    }
    else if ([value isEqualToString:@"女"]) {
        return @"female";
    }
    else {
        return @"unknown";
    }
}] subscribe:englishChannelT];

// 4
[[[englishChannelT skip:1] map:^id(NSString *value) {
    NSLog(@"here set chineseSex happen once");
    if ([value isEqualToString:@"male"]) {
        return @"男";
    }
    else if ([value isEqualToString:@"female"]) {
        return @"女";
    }
    else {
        return @"未知";
    }
}] subscribe:chineseChannelT];

解释一下:

  1. 仍然仅仅为了展示,与绑定无关,不变;
  2. 使用宏定义得到两个RACChannelTerminal类型的实例对象;
  3. 在两个终端之间搭建一条管线供数据流动,并且数据流动过程中会进行转换;
  4. 抛弃englishSex的初始值,使用chineseSex的初始值,所以有skip:1的操作,可以等价理解成在“=”前面。

实际运行一下,完美的实现了需求。Perfect!

获得RACChannelTerminal类型对象的三种方法

方法一:就是上面展示的直接使用RACChannelTo宏定义。

方法二:RACChannelTo宏定义存在的意义是为了简化RACKVOChannel类的使用方式,所以也可以直接使用这个类。

举个🌰:

RACChannelTo(view, property) = RACChannelTo(model, property);

完全等价于

[[RACKVOChannel alloc] initWithTarget:view keyPath:@"property" nilValue:nil][@"followingTerminal"] = 
[[RACKVOChannel alloc] initWithTarget:model keyPath:@"property" nilValue:nil][@"followingTerminal"];

方法三:RAC库为一些系统UI控件拓展了属性或者方法可以得到RACChannelTerminal类型对象。

随便举几个例子:

UITextField的拓展:

- (RACChannelTerminal *)rac_newTextChannel;

UISwitch的拓展:

- (RACChannelTerminal *)rac_newOnChannel;

UIControl的拓展:

- (RACChannelTerminal *)rac_channelForControlEvents:(UIControlEvents)controlEvents key:(NSString *)key nilValue:(id)nilValue;

再举个绑定View(UITextField)和Model的例子:

RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
RACChannelTerminal *modelChannelT = RACChannelTo(self.userModel, starCount);
[modelChannelT subscribe:textFieldChannelT];
[[textFieldChannelT skip:1] subscribe:modelChannelT];

或:

RACChannelTerminal *textFieldChannelT = self.textField.rac_newTextChannel;
RAC(self.userModel, starCount) = [textFieldChannelT skip:1];
[RACObserve(self.userModel, starCount) subscribe:textFieldChannelT];

数据流向如下图:

虽然大多数情况下,双向绑定都是为了连接一个View和一个Model,但是其实无论是同一个对象的两个不同属性,或是两个不同对象之间的属性,两个对象是两个Model还是两个View还是一个Model一个View,这些都是表象,归根结底连接的都是两个不同的属性。

常用宏定义

前面都介绍了,一张图总结:

常用Category

前面已经或多或少的提到了RAC为系统类创建的一些category,这大大方便了使用者。下面总结一下我认为最常用的。

UIKit Category

对UI库中文件的拓展。

rac_textSignal

前面用过太多次,相信已不再需要解释了。

[self.textField.rac_textSignal subscribeNext:^(id x) {
    NSLog(@"x is: %@",x);
}];

当在输入框中输入“I am just look look.”这样一段字符串的过程中,输出结果是:

x is:
x is: I
x is: I
x is: I a
x is: I am
x is: I am
x is: I am j
x is: I am ju
x is: I am jus
x is: I am just
x is: I am just
x is: I am just l
x is: I am just lo
x is: I am just loo
x is: I am just look
x is: I am just look
x is: I am just look l
x is: I am just look lo
x is: I am just look loo
x is: I am just look look
x is: I am just look look.    

rac_signalForControlEvents:

[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    NSLog(@"按钮被点击了,爱干点啥儿干点啥儿");
}];

应用这种方式,按钮点击的处理可以和按钮创建在一块儿,不必跨方法了。点击时输出结果是:

按钮被点击了,爱干点啥儿干点啥儿

rac_prepareForReuseSignal

无需多言,通常搭配takeUntil操作符使用。

rac_command

// 为了模拟,简陋了一些,领会精神
RACSignal *executeSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [[RACScheduler mainThreadScheduler] afterDelay:1.0 schedule:^{
        [subscriber sendNext:@"🍎"];
        [subscriber sendCompleted];
    }];
    return nil;
}];
RACCommand *executeCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    NSLog(@"get input value is: %@",input);
    return executeSignal;
}];
// 赋给属性值
self.button.rac_command = executeCommand;
// 余下使用方式不变
[executeCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
    NSLog(@"get result value is: %@",x);
}];

按钮被点击时输出结果是:

get input value is: <UIButton: 0x7f970dc1c690; ... >
get result value is: 🍎

一张图总结:

Foundation Category

对Foundation库中文件的拓展。

rac_willDeallocSignal

同样,无需多言,通常搭配takeUntil操作符使用。

rac_signalForSelector:

直接看一个例子:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(RACTuple *args) {
        NSLog(@"viewWillAppear method in signal happen animated: %@",args.first);
    }];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear method happen animated: %d",animated);
}

输出结果是:

viewWillAppear method happen animated: 1
viewWillAppear method in signal happen animated: 1

解释一下:可以将任意方法转化成一个信号,然后去订阅它。这样跨方法执行的代码就处在同一个上下文环境中,省去了将一些共用参数传来传去的烦恼。

这是原方法实现了的情况,其实还可以仅让原方法做声明,不实现。

看另一个例子:

在.h文件中声明一个方法:

- (void)demoMethodWithParam:(id)value;

在.m文件中:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[self rac_signalForSelector:@selector(demoMethodWithParam:)] subscribeNext:^(RACTuple *args) {
        NSLog(@"demoMethodWithParam in signal happen param is: %@", args.first);
    }];
    [self demoMethodWithParam:@"我就是个🌰"];
}

输出结果是:

demoMethodWithParam in signal happen param is: 我就是个🌰

解释一下:这种原方法未做实现的情况能正常运行需要满足两个条件:1 无返回值;2 没有入参或者入参是id类型

rac_liftSelector:withSignals:

RACSignal还有另外一种用法:

RACSignal *textColorSignal = [RACSignal return:[UIColor blueColor]];

和下面这种写法几乎是等价的:

RACSignal *textColorSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:[UIColor blueColor]];
    [subscriber sendCompleted];
    return nil;
}];

于是可以用如下方式设置一个按钮的颜色值:

[self.button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil];

顺便提一下,rac_liftSelector:withSignals:返回值也是一个信号,当然也可以被订阅:

[[self.button rac_liftSelector:@selector(setTitleColor:forState:) withSignals:textColorSignal, [RACSignal return:@(UIControlStateNormal)], nil] subscribeNext:^(id x) {
    NSLog(@"lift selector return value is: %@",x); // RACUnit 代表RAC中的空值类型
}];

输出结果是:

lift selector return value is: <RACUnit: 0x604000017270>

RACUnit是RAC中除RACEvent和RACTuple以外的第三种“值类型”,代表空。当liftSelectorSelector返回值是void类型时就会得到RACUnit类型的一个单例。

rac_sequence

如果你用过swift,再回来用OC,那么一定会为Foundation库中没有map、filter、reduce等高级函数而感到不习惯。为了简化OC中的集合操作,RAC定义了另一个功能强大的类–RACSequence。它和RACSignal有很多相似之处,因为两者继承于同一个父类–RACStream。所以它能使用很多之前介绍的RACSignal可以使用的方法,比如map,filter、ignore、skip、take、concat等等;而且两者还可以相互转换。

RAC为Foundation库中的集合类(如NSArray,NSDictionary,NSSet,NSString等)都拓展了一个RACSequence类型的属性叫做:rac_sequence

随便举个例子:

NSArray *array = @[@1, @2, @3, @4];
NSArray *newArray = [array.rac_sequence map:^id(NSNumber *value) {
    return @(100+value.integerValue);
}].array;

看着有没有舒服很多?

RACSequence和RACSignal归根结底都是流,但两者间最大的不同在于前者是pull-driven类型的流,而后者是push-driven类型的流。

再多内容就不说了,推荐两篇写的不错的文章:

Pull-Driven 的数据流 RACSequence
ReactiveCocoa 中 集合类RACSequence 和 RACTuple底层实现分析

一张图总结:

MVVM

RAC如何结合MVVM使用这个话题太大了,一两句话真是说不清楚。可以看下我之前翻译的两篇国外大神写的文章:

MVVM & ReactiveCocoa入门教程-第一部分
MVVM & ReactiveCocoa入门教程-第二部分

数据流向图可以看这篇:

iOS开发下的函数响应式编程

具体使用Demo可以看这个:

ReactiveCocoaDemo

总之两者搭配使用就像豆浆油条,简直绝配。

最后,再来看一眼文章开头出现过的图片,有没有感觉不过如此?

至此,三篇使用教程(我的学习历程)就结束了,能读到这里相信你或多或少都有所收获。但是要注意这还仅仅是皮毛,想了解更多自己去实践吧哈哈。Have Fun!

参考链接

官方文档:

DesignGuidelines
FrameworkOverview

源码解析:

RACSignal的Subscription深入分析
ReactiveCocoa核心元素与信号流

多线程:

ReactiveCocoa中RACScheduler是如何封装GCD的

双向绑定:

RAC中的双向数据绑定RACChannel
Bi-directional Data Bindings in ReactiveCocoa with RACChannel

我之前写的:

ReactiveCocoaDemo
RxSwift基本概念与使用
RxSwift进阶,细节与UI绑定
ReactiveObjC基本概念与简单使用
ReactiveObjC中的冷热信号与命令