
本文知识点:RACSignal使用、combine、@weakify@strongify、Model与UI双向绑定。
1. 介绍
ReactiveCocoa 接管了苹果的事件机制,asyncDisplayKit接管了苹果的UIKit。
- 运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术。
- Hook用处:截获API调用的技术。
- Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。
1.1 响应式
使用RAC的原因:实现响应式编程。
- 什么是响应式编程:
b = 2; c = 3;
a = b + c; //a = 5;
b = 100; //此时对修改 b 的修改并不会使 a 发生改变。
响应式:当修改b或c的时候,a同时发生变化。
- iOS开发中实现响应式
- 方法一:使用KVO监听对象的属性值达到这一效果。但缺点是KVO会统一调用同一个方法,如果监听属性过多,方法非常难以维护。
- 方法二:ReactiveCocoa是目前实现响应式编程的唯一解决方案。
1.2 难点
- 学习曲线陡峭
- 在团队开发的时候要特别谨慎
- 需要不断的代码评审,保证团队的代码风格一致
- 开发的时候调用堆栈深不见底,提高debug成本
1.3 框架导入
Cocoapods导入ReactiveCocoa5.0以上版本需注意:
- Swift 项目:使用 ReactiveCocoa
。但是 RAC 依赖于 ReactiveSwift,等于你引入了两个库。
pod 'ReactiveCocoa'
- OC 项目:使用 ReactiveObjC
。这个库里面包含原来 RAC 2 的全部代码。
pod 'ReactiveObjC'
- Swift 与 OC 混编:同时引用ReactiveCocoa、ReactiveObjC 和 ReactiveObjCBridge。
pod 'ReactiveObjC'
pod 'ReactiveCocoa'
pod 'ReactiveObjCBridge'
2. 使用

2.1 RACSignal
1> 信号的创建
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"123"];
[subscriber sendNext:@"456"];
NSLog(@"%@",subscriber);
// 如果不再发送数据,最后发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
[subscriber sendError:[NSError errorWithDomain:@"send error" code:0 userInfo:@{}]];
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不再被订阅了。
// 信号销毁的时候 会执行这个闭包
// 用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
// 使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
NSLog(@"dispose");
}];
}];
//信号被订阅。订阅者不是信号本身 而是这段代码所处的那个objcect 如在vc中,订阅者就是vc
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅next :%@",x);
}];
[signal subscribeError:^(NSError * _Nullable error) {
NSLog(@"订阅error :%@",x);
}];
[signal subscribeCompleted:^ {
NSLog(@"订阅completed");
}];
Output:
订阅next :123 //订阅的next信号
订阅next :456
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
//订阅的error信号(因为代码顺序),但是此时已无效,因为subscriber发出了completed信号
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
订阅completed
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
- RACSignal是冷信号,只有被订阅了才可以工作。
- RACSubscriber:表示订阅者的意思,用于发送信号,
这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者
。通过create创建的信号,都有一个订阅者,帮助他发送数据。 - RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
2> 控件的监听
UI类RAC自动封装了一些方法,使用的时候先去框架源文件中找。
以 button 为例:
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"%@",error);
} completed:^{
NSLog(@"completed");
}];
3> 信号的合并
类似元组类型,可以一次订阅多个信号。
//信号合并
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals;
//reduce中可以通过接受的参数进行计算,并且返回需要的数值
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(RACGenericReduceBlock)reduceBlock;
Ex1: //元组类型
[[RACSignal combineLatest:@[[name_textField rac_textSignal], [pwd_textField rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@ %@", x.first, x.second);
}];
Ex2:
//例如订阅username、password,reduce里面判断只有用户名和密码同时存在才允许登录
RACSignal *signal_username = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"abc"];
return nil;
}];
RACSignal *signal_pwd = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"123"];
return nil;
}];
//注意 参数需要自己添加上去
[[RACSignal combineLatest:@[signal_username, signal_pwd] reduce:^id _Nonnull (NSString *username, NSString *password){
return @(username.length > 0 && password.length > 0);
}] subscribeNext:^(id _Nullable x) {
//x是上面的@(bool)
NSLog(@"%@", x);
}];
Output: 1
4> RAC中的循环引用
因为系统提供的信号是始终存在的,因此在RAC中所有的block中,如果出现self.
或_成员变量
,几乎百分之百会循环引用。
解决办法:weak-strong dance(RAC提供了宏 @weakify & @strongify)
2.2 RAC双向绑定
需要用到两个重要宏: RAC()、RACObserve()
,源码如下:
#define RAC(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RAC_(TARGET, __VA_ARGS__, nil)) \
(RAC_(TARGET, __VA_ARGS__))
#define _RACObserve(TARGET, KEYPATH) \
({ \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})
#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
_RACObserve(TARGET, KEYPATH) \
_Pragma("clang diagnostic pop") \
})
#endif
1> Model -> UI (利用KVO宏)
RAC(TARGET, ...) = RACObserve(TARGET, KEYPATH);
如果使用基本数据类型绑定UI内容,需要使用map函数,通过block对value的数值进行转换后才能够绑定。
RAC(name_textField, text) = RACObserve(_person, name);
//rac中传递的数据都是id类型,如果是基本类型,需要使用map函数,通过block对value的数值进行转换后才能够绑定。
RAC(age_textField, text) = [RACObserve(_person, age) map:^id _Nullable(id _Nullable value) {
return [value description];
}];
2> UI -> Model (订阅控件发出的signal)
@weakify(self);
[[RACSignal combineLatest:@[[name_tf rac_textSignal], [age_tf rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
@strongify(self);
self.person.name = x.first;
self.person.age = [x.second integerValue];
}];
// 或
RAC(_person, name) = name_tf.rac_textSignal;
RAC(_person, age) = age_tf.rac_textSignal;
3. MVVM+RAC

网友评论