序言:
正如标题所说:人生苦短,我用RAC。
我们以往常用的代理方法、block 回调、target-action 机制、通知、KVO等等都可以用RAC代替,并且RAC的代码更精简,让人爱不释手。但不建议初学者使用,因为RAC的精简态度,让你忘记苹果原生的代码!!!
一张图纵览RAC架构
看到上面的图之后,会有一定的全局观,这里建议大家好好细读雷纯锋对RAC的源码解析的文章:ReactiveCocoa v2.5 源码解析之架构总览
这篇文章主要是实验用RAC代替以往常用的行为:代理、通知等。
一、RAC替代代理方法
以往我们在一个View上点击它的Button,会在ViewController实现它的功能,这个过程需要做代理方法,如下:
新建一个MainView类继承UIView,在其.h文件中,敲下如下代码
#import <UIKit/UIKit.h>
// 声明代理
@protocol MainViewDelegate <NSObject>
@required
- (void)btnPressed;
@end
@interface MainView : UIView
// 定义一个weak属性的delegate
@property (weak, nonatomic) id<MainViewDelegate> delegate;
@end
MainView.m
#import "MainView.h"
@implementation MainView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.frame = frame;
// 创建UI
[self createUI];
}
return self;
}
// MARK:- 创建UI
- (void)createUI {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(self.frame.size.width * 0.5 - 280 * 0.5, 100, 280, 30)];
[btn setTitle:@"我是小蘑菇" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[btn addTarget:self action:@selector(btnPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
// 点击Btn
- (void)btnPressed {
if (_delegate && [_delegate respondsToSelector:@selector(btnPressed)]) {
[_delegate btnPressed];
}
}
@end
ViewController.m
// 导入MainView
#import "MainView.h"
// 遵守协议
@interface ViewController () <MainViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 代理方法
[self delegateTest];
}
// MARK:- 代理方法
- (void)delegateTest {
// MainView实例化
MainView *mainView = [[MainView alloc] initWithFrame:self.view.frame];
mainView.delegate = self;
[self.view addSubview:mainView];
}
// MARK:- <MainViewDelegate>
- (void)btnPressed {
self.view.backgroundColor = [UIColor orangeColor];
}
@end
程序Run后,点击按钮,当前view的backgroundColor
会随之改变。但是有没有发现以上的步骤有点多?如果用RAC呢?
用RAC前,在你创建好的Podfile,添加
pod 'ReactiveObjC', '~> 3.0.0'
,接着pod install
以上操作完毕后,进入MainView.h文件,修改如下
#import <UIKit/UIKit.h>
//@protocol MainViewDelegate <NSObject>
//
//@required
//- (void)btnPressed;
//
//@end
@interface MainView : UIView
//@property (weak, nonatomic) id<MainViewDelegate> delegate;
@end
MainView.m的修改
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.frame = frame;
// 创建UI
[self createUI];
}
return self;
}
// MARK:- 创建UI
- (void)createUI {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(self.frame.size.width * 0.5 - 280 * 0.5, 100, 280, 30)];
[btn setTitle:@"我是小蘑菇" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// [btn addTarget:self action:@selector(btnPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
// - (void)btnPressed {
// if (_delegate && [_delegate respondsToSelector:@selector(btnPressed)]) {
// [_delegate btnPressed];
// }
// }
接着进入ViewController.m文件,不需要遵守协议,只在要实现的方法加入以下代码:
// 导入ReactiveObjC.h
#import "ReactiveObjC.h"
// 实现方法
- (void)delegateTest {
MainView *mainView = [[MainView alloc] initWithFrame:self.view.frame];
[self.view addSubview:mainView];
[[mainView rac_signalForSelector:@selector(btnPressed)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@", [NSThread currentThread]);
self.view.backgroundColor = [UIColor orangeColor];
}];
}
替代代理方法实验效果成功。这里说下:1.subscribeNext
block下返回的是主线程,2.不用担心循环引用问题,RAC已经帮我们考虑好了。
二、监听点击事件
MainView.h,添加这一句
@property (strong, nonatomic) UIButton *btn;
在MainView.m 的createUI的方法中,添加并修改👇🏻
// MARK:- 创建UI
- (void)createUI {
UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(self.frame.size.width * 0.5 - 280 * 0.5, 100, 280, 30)];
[btn setTitle:@"我是小蘑菇" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
// [btn addTarget:self action:@selector(btnPressed) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
self.btn = btn;
}
ViewController.m
MainView *mainView = [[MainView alloc] initWithFrame:self.view.frame];
[self.view addSubview:mainView];
[[mainView.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"x: %@", x);
}];
点击事件实验成功,是不是很精简?
三、替代KVO
如果需要了解KVO可以看 杨萧玉的《Objective-C中的KVC和KVO》
基于以上,在ViewController.m文件添加如下代码
- (void)kvoTest {
MainView *mainView = [[MainView alloc] initWithFrame:self.view.frame];
[self.view addSubview:mainView];
// 点击按钮
[[mainView.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
mainView.backgroundColor = [UIColor redColor];
}];
// KVO 记得移除KVO监听backgroundColor属性
[mainView addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听到了%@的%@属性发生了改变", object, keyPath);
NSLog(@"%@", change);
}
上面我们设定点击按钮后改变mainVie的颜色,并且用KVO监听了背景颜色,运行后:
这里用RAC代替可这样修改:
- (void) kvoTest {
MainView *mainView = [[MainView alloc] initWithFrame:self.view.frame];
[self.view addSubview:mainView];
// 点击按钮
[[mainView.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
mainView.backgroundColor = [UIColor redColor];
}];
// KVO
// [mainView addObserver:self forKeyPath:@"backgroundColor" options:NSKeyValueObservingOptionOld context:nil];
// RAC 这里是不需要做移除操作的
[[mainView rac_valuesForKeyPath:@"backgroundColor" observer:self] subscribeNext:^(id _Nullable x) {
if (x) {
NSLog(@"%@",x);
}
}];
}
//- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
// NSLog(@"监听到了%@的%@属性发生了改变", object, keyPath);
// NSLog(@"%@", change);
//}
实验效果完成!
当然了,如果你的项目是用了MVVM模式,可以在 MVVM 中使用 RACSubject 实现统一的错误处理逻辑。比如,我们可以在 viewModel 的基类中声明一个 RACSubject 类型的属性 errors ,然后在 viewController 的基类中编写统一的错误处理逻辑:
[self.viewModel.errors subscribeNext:^(NSError *error) {
// 错误处理逻辑
}
采摘上面推荐的雷纯锋的原话,有修改