您的位置:首页 > Web前端 > React

ReactiveCocoa学习笔记

2016-11-30 16:03 15 查看
最近在学习RAC框架,断断续续的看了好久,看的文章博客可以说少说也得有一两百,所以这是一个综合的产物,也可以算是一个学习笔记,毕竟好记性不如烂笔头,记录下来,以供以后复习。如有侵权,联系立删。

书归正传:

RAC简介

RAC是一个F(functional)R(reactive)P(programming)框架,可以很好的结合MVVM. 写出的项目易于维护。但是导入MVVM和RAC的项目,学习成本有点高,如果自己独立开发,或者团队内成员水平较平均且普遍较高,那么可以尝试这么做。

注意:

RAC框架已经有几年的历史了,理所当然的有OC,Swift版本,
从2.5版本之后,就开始全面支持swift,不再支持OC
,所以如果你的项目是OC的项目,
请使用cocoapods导入2.5或之前的版本
,如果你对cocoapods不甚了解,请移步我的cocoapods踩坑:

另外需要了解的:

MVVM设计模式。

相信大家对MVC肯定都很熟悉,Model业务模型,View前端显示,Controller控制器。

但是一般我们在开发的时候,(仅针对iOS开发)M层一般只会写有几个成员属性,也就是说,所有的业务逻辑,网络层代码之类的,全部都写在了Controller中,这样,就造成了C层中有大量的,冗杂的代码,让人维护起来很费劲,本着‘高内聚,低耦合’的原则,有些大神们就想到了MVVM的设计模式。

本人理解

如有不对,还请各位大神批评指正。

MVVM其实是M-V-C-VM,也就是比正常的MVC多了一个ViewModel,

VM从网络请求数据,然后参照Model的格式和一些需求,处理数据,然后返回给Controller,再传递给View展示出来
,大概就是图中的画出来的样子



ViewModel主要从事的业务

1. 从网络请求、处理数据,然后返回给Controller,controller再传递给View层显示

2. 界面中的业务逻辑

也就是把以前Controller中的逻辑代码,换了个地方,全部写在了ViewModel中了。

所以,从根本上来说,MVC和MVVM是可以相互转换的。

下面从使用中来了解RAC。

重要的类

RACSignal

这个是RAC中最重要的一个类了(RAC中全部都是以信号来处理的)

信号分为热信号和冷信号,默认创建完成之后是冷信号,这个时候是无用的,也就是值改变了,也不会触发,只有订阅
subscribe
了之后,信号才能变成热信号,订阅的block(
subscribeNext
)才会被调用。

创建信号的方法:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;


可以看到返回了一个block,该block返回值为RACDisposable,参数为subscriber,该参数遵守RACSubscriber协议

RACDisposable,如果想要取消订阅,使用此类的方法
- (void)dispose;


RACSubscriber协议中的方法有:

- (void)sendNext:(id)value;


- (void)sendError:(NSError *)error;


- (void)sendCompleted;


用来传值,或者返回数据。

订阅信号的方法

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock


该方法是
RACSignal
subscription类别中的一个方法。

调用之后,冷信号变成热信号,
nextBlock
会执行

使用中要注意:

1. 在block中,如果订阅之后,不想传值,可以使用协议中的方法

[subscriber sendNext:nil]
,若想传,则在该方法后传入参数(必须id类)

2. 传值完成之后,必须调用
sendCompleted
方法,以示订阅完成,否则,无法再次订阅信号。

3. 如果返回值为ERROR,且要传递,调用
sendError
方法,
sendNext
方法无法传递错误信息

了解了信号和订阅,我们就可以开始简单的使用一下了。

假如界面中有一个textview一个label. 我们可以绑定他们两个,以达到用户在textview中输入的值,显示在label中

//界面的布局之类的不再写,只写下逻辑块代码
[self.textview.rac_textSignal subscribeNext:^(id x) {
self.label.text = (NSString *)x;
}];


代码中的rac_textSignal返回的是textView的文本信号。我们订阅之后,就可以获得这个信号,其中的
id x
就是textview中的文本,所以我们直接赋值给
label.text
就OK了

RACCommand

这个类,是我花了好久时间才看懂的一个类。主要从事事件的处理。

比如网络请求这种,一方传递出去一个信号,需要另一方返回一个信号的这种流程。前后端通信这种就是一个很好的例子: 前端post数据给后端,然后服务器返回给我们相应的JSON字符串,这个就可以使用RACCommand来处理。

与RACSignal的区别, RACSignal是单向的,只能后者订阅前者,反之则不行

创建方法

- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;


该方法只有一个 block参数,创建的时候,要在block中创建一个RACSignal,并返回,如果不想返回信号,则可以
return [RACSignal empty];
来返回一个空的信号。

- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;


该方法除了一个 block参数之外,还有一个enable信号,用来控制command能否执行。

执行方法

- (RACSignal *)execute:(id)input;


只有执行了,订阅的block才会调用

属性

RACCommand有几个属性

executionSignals

executing

errors

这些属性的类型都是RACSignal. 目的就是在执行之后,可以订阅,查看信号内的内容。

allowsConcurrentExecution BOOL类型的变量,是否允许同时多个执行,默认是NO

注意:

一般情况下,
[self.command.executionSignals subscribeNext:^(id x) {...}]
订阅的时候,里边的x,是
RACSignal
类的实例,我们此时订阅这个信号,就可以看到我们发出的信号(外层信号)的内容,如果想要看到里边包装的信号,也就是信号中的信号,我们可以使用

双重订阅

[self.command.executionSignals subscribeNext:^(RACSignal *x) {
[x subscribeNext:^(id x){
NSLog(@
4000
"%@",x);
}];
}]


RAC的高级用法

// switchToLatest 获取信号中信号最近发出信号,订阅最近发出的信号。
[[self.command.executionSignals switchToLatest]
subscribeNext:^(id x) {
NSLog(@"%@",x);
}];


switchToLatest方法: 获取信号中信号最近发出信号,订阅最近发出的信号。也就是可以跳过外层的信号,直接订阅内部的信号。

或者

//跳过第一次订阅,直接订阅第二次
[[self.command.executionSignals skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];


RACCommand的用法

//1,先创建
RACCommand *cmd = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"传值"];
[subscriber sendCompleted];
return nil;
}];
}];

//2. 订阅
//信号中的信号,要双层订阅
[cmd.executionSignals subscribeNext:^(id x) {
NSLog(@"%@",x);  //<RACDynamicSignal: 0x79e99050> name:
[x subscribeNext:^(id inner) {
NSLog(@"%@",inner);  //传值
}];
}];

//开始订阅的时候会打Log
[cmd.executing subscribeNext:^(id x) {
NSLog(@"executing");
}];

//如果出错了,会走这里
[cmd.errors subscribeNext:^(id x) {
NSLog(@"error");
NSLog(@"%@",x);
}];

//3. 执行
[cmd execute:@"执行"];


RACSubject

介绍

RACSubject:信号提供者,自己可以充当信号,又能发送信号。

使用步骤

创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。

订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

发送信号 sendNext:(id)value

一般用途

替换代理,block

//在第二个VC里边声明一个RACSubject属性,以反向传值为例,第二个页面的背景色为随机色,点击按钮POP回第一个页面,然后把颜色传给第一个页面
@interface SecViewController : UIViewController

@property (nonatomic,strong) RACSubject *delegateSubject;

@end

@implementation SecViewController

- (void)viewDidLoad {
[super viewDidLoad];

UIButton *btn = [[UIButton alloc]init];
[btn setTitle:@"点击传值" forState:UIControlStateNormal];
btn.frame = CGRectMake(0, 0, 100, 44);
btn.center = self.view.center;
[[btn rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(id x) {
if (self.delegateSubject) {
[self.delegateSubject sendNext:self.view.backgroundColor];
[self.delegateSubject sendCompleted];
}
[self.navigationController popViewControllerAnimated:YES];
}];
[self.view addSubview:btn];
}

//第一个页面的PUSH代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
SecViewController *sec = [[SecViewController alloc]init];
sec.delegateSubject = [RACSubject subject];
[sec.delegateSubject subscribeNext:^(id x) {
NSLog(@"%@",x);
self.view.backgroundColor = (UIColor *)x;
}];
sec.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:sec animated:YES];
}


可以在代码段里看到一个
[btn rac_signalForControlEvents:UIControlEventTouchUpInside]
,这个方法会返回按钮的command中的信号,系统默认为我们做的,我们只要订阅这个信号,就可以执行按钮对应的Action了

RACTuple

RAC中的元组类,用来包装值,类似NSArray

创建方法

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert;
+ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;


属性

@property (nonatomic, readonly) NSUInteger count;

/// These properties all return the object at that index or nil if the number of
/// objects is less than the index.
@property (nonatomic, readonly) id first;
@property (nonatomic, readonly) id second;
@property (nonatomic, readonly) id third;
@property (nonatomic, readonly) id fourth;
@property (nonatomic, readonly) id fifth;
@property (nonatomic, readonly) id last;


所以我们可以使用索引来获取Tuple中的值

NSArray *testArr = @[@"1",@"2",@"3",@"4"];
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:testArr];
NSLog(@"%@",tuple[0]);    //1
NSLog(@"%@",tuple[1]);    //2
NSLog(@"%@",tuple.third); //3


如果遇到NSDictionary,则RACTuple则会循环键值对的个数次,倒叙的遍历字典的键值对(通常是把一个键值对当成数组的一个元素)

NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)};
[testDic.rac_sequence.signal subscribeNext:^(RACTuple *x) {
NSLog(@"%@",x);
NSLog(@"%ld",(long)x.count);
NSLog(@"%@",x[0]);
NSLog(@"%@",x[1]);
NSLog(@"%@",x.third);
}];

循环 第一次,log结果:(<RACTuple: 0x79e49550> (age,20)   2   age   20   (null))
第二次结果:(<RACTuple: 0x7c183bc0> (name,testData)   2   name   testData   (null))


我们可以使用RACTupleUnpack把tuple转换成dictionary的键值关系

RACTupleUnpack(NSString *key, NSString *value) = x;


上面的写法和下面的得到的结果是一样的

NSString *key = x[0];
NSString *value = x[1];


RACSequence

简介

RAC中快速遍历Array和Dictionary的类

用途

遍历数组字典

    NSArray *testArr = @[@"1",@"2",@"3",@"4"];

[testArr.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x); //<RACTuple: 0x796386c0> ( number,1 )
}];

NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)};

[testDic.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
#if 0
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"%@,%@",key,value);
#else
NSString *key = x[0]; NSString *value = x[1];
NSLog(@"%@,%@",key,value);
#endif
}];


字典转模型

//OC写法
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];

NSMutableArray *items = [NSMutableArray array];

for (NSDictionary *dict in dictArr) {
FlagItem *item = [FlagItem flagWithDict:dict];
[items addObject:item];
}

//RAC写法
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];

NSMutableArray *flags = [NSMutableArray array];

_flags = flags;

// rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
[dictArr.rac_sequence.signal subscribeNext:^(id x) {
// 运用RAC遍历字典,x:字典

FlagItem *item = [FlagItem flagWithDict:x];

[flags addObject:item];

}];

NSLog(@"%@",  NSStringFromCGRect([UIScreen mainScreen].bounds));

//RAC高级写法:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
// map:映射的意思,目的:把原始值value映射成一个新值
// array: 把集合转换成数组
// 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {

return [FlagItem flagWithDict:value];

}] array];


rac_signalForSelector

简介

可以代替代理

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Message" message:@"You will push to the second ViewController,right?" delegate:self cancelButtonTitle:@"cacel" otherButtonTitles:@"okay", nil];
#pragma clang diagnostic pop
[alert show];

[[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *x) {
NSLog(@"%@",x);
if ([x[1] boolValue]) {
[self pushToSecViewController];
}
}];

//AlertView的简化写法
[[alertView rac_buttonClickedSignal] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];


self.textField.delegate = self;

[[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(id x) {
NSLog(@"开始编辑");
}];

[[self rac_signalForSelector:@selector(textFieldDidEndEditing:) fromProtocol:@protocol(UITextFieldDelegate)]
subscribeNext:^(id x) {
NSLog(@"结束编辑");
}];


跟RACSubject区别

RACSubject可以传值

rac_signalForSelector: fromProtocol: 不可以传值

代替KVO

rac_valuesAndChangesForKeyPath

// 把监听view的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[view rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {

NSLog(@"%@",x);

}];


代替通知

rac_addObserverForName

// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];


处理当界面有多次请求时,需要都获取到数据才能进行下一步

rac_liftSelector:withSignalsFromArray:Signals

当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法

//处理多个请求,都返回结果的时候,统一做处理.
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) {

// 发送请求1
[subscriber sendNext:@"发送请求1"];
return nil;
}];

RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) {
// 发送请求2
[subscriber sendNext:@"发送请求2"];
return nil;
}];

// Selector调用:当所有信号都发送数据的时候调用
// 数组存放信号
// Selector注意点:参数根据数组元素决定
// Selector方法参数类型,就是信号传递出来数据
[self rac_liftSelector:@selector(doingSthWithData1: data2:) withSignalsFromArray:@[signal1,signal2]];


RAC常见宏

RAC(target, keyPath)

用法

用于给某个对象的某个属性绑定。

例子

// 只要文本框文字改变,就会修改label的文字
RAC(self.labelView,text) = _textField.rac_textSignal;


RACOberve(self, name)

用法

监听某个对象的某个属性,返回的是信号。

例子

[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];


先写到这里,回头会有补充

本文demo地址

另外还有一个使用RAC+MVVM模拟的登录小demo

参考资料:

学习RAC小记-适合给新手看的RAC用法总结

ReactiveCocoa(FRP)-进阶篇

被误解的 MVC 和被神化的 MVVM

ReactiveCocoa干货
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mvvm 框架 RAC