ReactiveCocoa-一个你值得关注的东西-例子

一、ReactiveCocoa是什么?

ReactiveCocoa是最近在GitHub上非常火的一个框架,是基于响应式编程开发的。ReactiveCocoa

关于响应式编程,这个维基百科有解释:响应式编程

维基百科举的例子是这样:

在代码中有a=b+c,一旦执行后,b或者c变化,除非再次执行一遍,a不会再变化。而响应式编程的思想,就是b或者c变化时,a的值也随之变化。就像Excel表格中,如果在C1中写入公式:=SUM(A1;B1),也就是说无论A1或者B1发生多少变化,C1值总会根据这个公式自己做变化。为什么微软这么屌,因为响应式编程就是最先从微软实验室得出来的。请参考:Reactivite Extension


二、我写这篇博客的目的

因为我用了,发现不仅从上手程度、功能性、逻辑性、易用性上都非常出色。你可以不必须掌握它,因为目前好像还没有产品是用它来开发的,而且这个ReactiveCocoa还在不断的扩展中。

三、举个例子吧

我有这么一个需求:

1、2个输入框,1个按钮

2、第一个输入框只有满足输入:"我爱刘力"后左侧图片显示对勾。

3、只有第一个满足条件了,第二个才可以输入。而且第二个必须输入的内容不和第一个相同,但最后两个字包含“刘力”,左侧的图片才显示对勾。

4、只有前2个输入框都符合条件了,按钮才可以点击enable=YES

5、按钮点击后出来趣味Alert框。


如图:

图一 都不满足条件

图2 只有第一个输入框满足条件后,第二个输入框变化placeholder,并可以输入

图3 都符合条件时,按钮可以点击

图4 点击按钮弹出Alert,选择想或者不想

思考:很简单的一个例子。如果我们常规来写的话,我觉得应该是这样的顺序:

1、第一个textfield的代理方法中进行判断,如果符合条件,出现对勾,将第二个textfield替换Placeholder,并可输入。

2、同1,在第二个textfield的代理方法中进行判断,如果符合,将按钮enable置为YES,替换Title和按钮上的颜色

听起来也不是很复杂,但我们看看如果用ReactiveCocoa来写会是如何的?

这里需要指出,ReactiveCocoa引用了信号源这么一个东西,关于这个解释,我直接引用某位大神的说法:

ReactiveCocoagithub去年开源的一个项目,是在iOS平台上对FRP的实现。FRP的核心是信号,信号在ReactiveCocoa(以下简称RAC)中是通过RACSignal来表示的,信号是数据流,可以被绑定和传递。
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。

首先,我们需要引入ReactiveCocoa框架,这里我用的CocoaPods来管理的,如果你不知道CocoaPods的话,看看唐巧的博客:使用CocoaPods来做iOS程序的包依赖管理

开始上代码:

#import "TableViewViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>

//下面的两张图用在是否符合要求
#define Wrong [UIImage imageNamed:@"checkbox"]
#define Right [UIImage imageNamed:@"checkSelected"]


@interface TableViewViewController ()

@property(nonatomic,retain)UITextField *textFieldOne;

@property(nonatomic,retain)UITextField *textFieldTwo;

//创建2个输入框的信号源
@property(nonatomic,strong)RACSignal *textFieldOneSignal;
@property(nonatomic,strong)RACSignal *textFieldTwoSignal;

//创建一个合并的信号源
@property(nonatomic,strong)RACSignal *bothTwoTextFieldSignal;


@end

@implementation TableViewViewController

- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}

- (void)viewDidLoad
{
[super viewDidLoad];

// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}

- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 3;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cell.selectionStyle = UITableViewCellSelectionStyleNone;

if (indexPath.row == 0) {
self.textFieldOne = [self creatTextFieldWithView:cell.contentView WithPlaceHolderStr:@"请输入:我爱刘力"];
UIImageView * imageView = [self creatImageViewWithView:self.textFieldOne];
self.textFieldOne.leftView = imageView;
//信号创建
self.textFieldOneSignal = [self.textFieldOne.rac_textSignal map:^id(NSString * value) {
//返回下面这两个判断的值
return @((value.length>=1)&&([value isEqualToString:@"我爱刘力"]));
}];
//map是改变自己的需求
RAC(imageView,image) = [self.textFieldOneSignal map:^id(NSNumber * value) {
if (value.boolValue) {
return Right;
}
return Wrong;
}];

}

if (indexPath.row == 1) {

self.textFieldTwo = [self creatTextFieldWithView:cell.contentView WithPlaceHolderStr:@"不能和上一个一样,但最后两字必须含“刘力”"];
UIImageView * imageView = [self creatImageViewWithView:self.textFieldTwo];
self.textFieldTwo.leftView = imageView;

//将输入框2的enable属性和输入框1的信号源进行等价

//将输入框2enable属性和输入框1的信号源进行等价

RAC(self.textFieldTwo,enabled) = self.textFieldOneSignal;

[self.textFieldOneSignal subscribeNext:^(NSNumber *x) {

if (![x boolValue]) {

self.textFieldTwo.enabled = NO;

self.textFieldTwo.placeholder = @"第一个不对,就没办法在我这输入,哈哈";

}else

{

self.textFieldTwo.enabled = YES;

self.textFieldTwo.placeholder = @"不能和上一个一样,但最后两字必须含刘力”";

}

}];



//信号创建
self.textFieldTwoSignal = [self.textFieldTwo.rac_textSignal map:^id(NSString * value) {
//返回下面这两个判断的值
return @((value.length>=1)&&([value hasSuffix:@"刘力"])&&(![value isEqualToString:@"我爱刘力"]));
}];
//map是改变自己的需求
RAC(imageView,image) = [self.textFieldTwoSignal map:^id(NSNumber * value) {
if (value.boolValue) {
return Right;
}
return Wrong;
}];


}



}

if (indexPath.row == 2) {

//在这个地方去指定合并信号源,因为前两个信号源已经生成
self.bothTwoTextFieldSignal = [RACSignal combineLatest:@[self.textFieldOneSignal,self.textFieldTwoSignal] reduce:^(NSNumber*textFieldOne,NSNumber*textFieldTwo){

//2个合并成一个,如果2个都符合条件,合并信号源也输出YES,否则NO
return @(textFieldOne.boolValue&&textFieldTwo.boolValue);
}];


UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted];
button.frame = cell.contentView.bounds;
[cell.contentView addSubview:button];

//将按钮的是否可点击与2个输入框是否符合条件进行绑定

RAC(button,enabled)=self.bothTwoTextFieldSignal;

//条件的扩展,改变颜色

[self.bothTwoTextFieldSignal subscribeNext:^(NSNumber *x) {

if (x.boolValue) {

[button setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];

[button setTitle:@"恭喜你,你可以点我" forState:UIControlStateNormal];


}else

{

[button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];

[button setTitle:@"哎,你不符合,不可以点我" forState:UIControlStateNormal];


}

}];


//将按钮的按下时进行绑定
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
//将按钮执行的方法放置这里面

UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"恭喜你" message:@"看来你是真心爱刘力,想成为神仙么?" delegate:self cancelButtonTitle:@"想" otherButtonTitles:@"不想",nil];
[alertView show];
[alertView.rac_buttonClickedSignal subscribeNext:^(NSNumber *x) {
if ([x isEqualToNumber:[NSNumber numberWithInteger:0]]) {
//这里点了第0个按钮

UIAlertView * alertViewOne = [[UIAlertView alloc]initWithTitle:@"你已经成为神仙了" message:@"你回家看看你床底下,多了500万,赏给你的" delegate:self cancelButtonTitle:@"哎呀,我回家了" otherButtonTitles:nil,nil];
[alertViewOne show];

}else
{
//这里点了第一个按钮
UIAlertView * alertViewTwo = [[UIAlertView alloc]initWithTitle:@"你不想当神仙?!!" message:@"你看看你的皮夹,没钱了吧!我变没了!" delegate:self cancelButtonTitle:@"我知道错了" otherButtonTitles:nil,nil];
[alertViewTwo show];

}
}];
}];


}


// Configure the cell...

return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}

#pragma mark- 创建输入框
-(UITextField*)creatTextFieldWithView:(UIView*)view WithPlaceHolderStr:(NSString*)placeHolderStr
{
UITextField * field = [[UITextField alloc]initWithFrame:view.bounds];
field.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
field.leftViewMode=UITextFieldViewModeAlways;
field.autocapitalizationType = UITextAutocapitalizationTypeNone;
field.autocorrectionType = UITextAutocorrectionTypeNo;
field.clearButtonMode = UITextFieldViewModeWhileEditing;
field.placeholder = placeHolderStr;
field.font = [UIFont systemFontOfSize:12];
[view addSubview:field];

return field;

}


#pragma mark- 创建imageview
-(UIImageView*)creatImageViewWithView:(UIView*)view
{
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(CGRectGetMinX(view.bounds),CGRectGetMinY(view.bounds),CGRectGetHeight(view.bounds),CGRectGetHeight(view.bounds))];

return imageView;

}



4、总结。

就针对这个例子来讲,我们用ReactiveCocoa会使代码简洁,可读性高。比如,textfield无需在代理里判断,button的响应方法直接用block来替代,alert的按下也无需在代理里面判断。最重要的是,信号源一旦变化,其对应的触发将同步变化。这是一个简单的Demo,后续会逐步研究比较高难度的,比如替换KVO、比如网络下载、比如替换NSOperation等等...

作为一个程序员,除了敲代码,也要密切跟踪最新技术走向,我觉得对我们是有好处的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom