本篇主要介绍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
操作符可以从源头改变事件流传递所在的线程;仅第一次使用有效。
总结:deliverOn
比subscribeOn
更加常用,并且当然了两者可混合使用。
顺便说一下: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
,其中redView
的alpha
初始值为0.5而greenView
的alpha
初始值为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];
解释一下:
- 仍然仅仅为了展示,与绑定无关,不变;
- 使用宏定义得到两个
RACChannelTerminal
类型的实例对象; - 在两个终端之间搭建一条管线供数据流动,并且数据流动过程中会进行转换;
- 抛弃
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以外的第三种“值类型”,代表空。当liftSelector
的Selector
返回值是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入门教程-第二部分
数据流向图可以看这篇:
具体使用Demo可以看这个:
总之两者搭配使用就像豆浆油条,简直绝配。
最后,再来看一眼文章开头出现过的图片,有没有感觉不过如此?
至此,三篇使用教程(我的学习历程)就结束了,能读到这里相信你或多或少都有所收获。但是要注意这还仅仅是皮毛,想了解更多自己去实践吧哈哈。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中的冷热信号与命令