文档章节

ReactiveCocoa,最受欢迎的iOS函数响应式编程库(2.5版),没有之一!

ios122
 ios122
发布于 2015/10/19 23:49
字数 2795
阅读 98
收藏 5
点赞 0
评论 0

简介

  • 项目主页: ReactiveCocoa
  • 实例下载: https://github.com/ios122/ios122
  • 简评: 最受欢迎,最有价值的iOS响应式编程库,没有之一!iOS MVVM模式的完美搭档,更多关于MVVM与ReactiveCocoa的讨论,参考这篇文章: 【长篇高能】ReactiveCocoa 和 MVVM 入门
  • 注意: ReactiveCocoa 最新3.0版本,使用Swift重写,最低支持iOS8.0,与国内大多数公司实际现状(一般要求最低兼容iOS7.0)不符;故此处选择兼容性版本更低的 2.5 版本来进行对译与解读.

系统要求

  • iOS 7.0 + (ReactiveCocoa 2.5 版本)

安装

推荐使用 CocoaPods 安装:

platform :ios, '7.0'

pod "ReactiveCocoa" # RAC,一个支持响应式编程的库.

入门

ReactiveCocoa 灵感来源于 函数响应式编程. ReactiveCocoa通常简称为RAC.RAC中,不再使用变量,而是使用信号(以 RACSignal为代表)来捕捉现在和未来的数据或视图的值.

通过对信号的链接,组合与响应, 软件就可以声明式的方式书写;这样就不再需要频繁地去监测和更新数据或视图的值了.

RAC 主要特性之一就是提供了一种单一又统一的方式来处理各种异步操作--包括代理方法,block回调,target-action机制,通知和KVO等.

这是一个简单的例子:

// 当self.username变化时,在控制台打印新的名字.
//
// RACObserve(self, username) 创建一个新的 RACSignal 信号对象,它将会发送self.username当前的值,和以后 self.username 发生变化时 的所有新值.
// -subscribeNext: 无论signal信号对象何时发送消息,此block回调都将会被执行.
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
	NSLog(@"%@", newName);
}];

但是和KVO不同的是, signals信号对象支持链式操作:

// 只打印以"j"开头的名字.
//
// -filter: 当其bock方法返回YES时,才会返回一个新的RACSignal 信号对象;即如果其block方法返回NO,信号不再继续往下传播.

[[RACObserve(self, username)
	filter:^(NSString *newName) {
		return [newName hasPrefix:@"j"];
	}]
	subscribeNext:^(NSString *newName) {
		NSLog(@"%@", newName);
	}];

Signals信号也可以用于派生属性(即那些由其他属性的值决定的属性,如Person可能有一个属性为 age年龄 和一个属性 isYong是否年轻,isYong 是由 age 属性的值推断而来,由age本身的值决定).不再需要来监测某个属性的值,然后来对应更新其他受此属性的新值影响的属性的值.RAC 可以支持以signales信号和操作的方式来表达派生属性.

// 创建一个单向绑定, self.password和self.passwordConfirmation 相等
// 时,self.createEnabled 会自动变为true.
//
// RAC() 是一个宏,使绑定看起来更NICE.
// 
// +combineLatest:reduce:  使用一个 signals 信号的数组;
// 在任意signal变化时,使用他们的最后一次的值来执行block;
// 并返回一个新的 RACSignal信号对象来将block的值用作属性的新值来发送;
// 简单说,类似于重写createEnabled 属性的 getter 方法.

RAC(self, createEnabled) = [RACSignal 
	combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] 
	reduce:^(NSString *password, NSString *passwordConfirm) {
		return @([passwordConfirm isEqualToString:password]);
	}];

// 使用时,是不需要考虑属性是否是派生属性以及以何种方式绑定的:
[RACObserve(self, createEnabled) subscribeNext: ^(NSNumber * enbable){
    NSLog(@"%@", enbable);
}];

Signals信号可以基于任何随时间变化的数据流创建,不仅仅是KVO.例如说,他们可以用来表示一个按钮的点击事件:

// 任意时间点击按钮,都会打印一条消息. 
//
// RACCommand 创建代表UI事件的signals信号.例如,单个信号都可以代表一个按钮被点击,
// 然后会有一些额外的操作与处理.
//
// -rac_command 是NSButton的一个扩展.按钮被点击时,会将会把自身发送给rac_command self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
	NSLog(@"button was pressed!");
	return [RACSignal empty];
}];

或者异步网络请求:

// 监听"登陆"按钮,并记录网络请求成功的消息.

// 这个block会在来任意开始登陆步骤,执行登陆命令时调用.

self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) {
   // 这是一个假想中的 -logIn 方法, 返回一个 signal信号对象,这个对象在网络对象完成时发送 值.
   // 可以使用 -filter 方法来保证当且仅当网络请求完成时,才返回一个 signal 对象.
	return [client logIn];
}];

// -executionSignals 返回一个signal对象,这个signal对象就是每次执行命令时通过上面的block返回的那个signal对象.
[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
    // 打印信息,不论何时我们登陆成功.
	[loginSignal subscribeCompleted:^{
		NSLog(@"Logged in successfully!");
	}];
}];

// 当按钮被点击时,执行login命令.
self.loginButton.rac_command = self.loginCommand;

Signals信号 也可以表示定时器,其他的UI事件,或者任何其他会随时间变化的东西.

在异步操作上使用signals信号,让通过链接和转换这些signal信号,构建更加复杂的行为成为可能.可以在一组操作完成后,来触发此操作即可:

// 执行两个网络操作,并在它们都完成后在控制台打印信息.
//
// +merge: 传入一组signal信号,并返回一个新的RACSignal信号对象.这个新返回的RACSignal信号对象,传递所有请求的值,并在所有的请求完成时完成.即:新返回的RACSignal信号,在每个请求完成时,都会发送个消息;在所有消息完成时,除了发送消息外,还会触发"完成"相关的block.
//
// -subscribeCompleted: signal信号完成时,将会执行block.
[[RACSignal 
	merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] 
	subscribeCompleted:^{
		NSLog(@"They're both done!");
	}];

Signals 信号可以被链接以连续执行异步操作,而不再需要嵌套式的block调用.用法类似于:

// 用户登录,然后加载缓存信息,然后从服务器获取剩余的消息.在这一切完成后,输入信息到控制台.
//
// 假想的 -logInUser 方法,在登录完成后,返回一个signal信号对象.
//
// -flattenMap: 无论任何时候,signal信号发送一个值,它的block都将被执行,然后返回一个新的RACSignal,这个新的RACSignal信号对象会merge合并所有此block返回的signals信号为一个RACSignal信号对象.
[[[[client 
	logInUser] 
	flattenMap:^(User *user) {
		// Return a signal that loads cached messages for the user.
		return [client loadCachedMessagesForUser:user];
	}]
	flattenMap:^(NSArray *messages) {
		// Return a signal that fetches any remaining messages.
		return [client fetchMessagesAfterMessage:messages.lastObject];
	}]
	subscribeNext:^(NSArray *newMessages) {
		NSLog(@"New messages: %@", newMessages);
	} completed:^{
		NSLog(@"Fetched all messages.");
	}];

RAC 甚至让绑定异步操作的结果也更容易:

// 创建一个单向的绑定,遮掩self.imagView.image就可以在用户的头像下载完成后自动被设置.
//
// 假定的 -fetchUserWithUsername: 方法返回一个发送用户对象的signal信号对象.
//
// -deliverOn: 创建一个新的 signals 信号对象,以在其他队列来处理他们的任务.
// 在这个示例中,这个方法被用来将任务移到后台队列,并在稍后下载完成后返回主线程中.
//
// -map: 每个获取的用户都会传递进到这个block,然后返回新的RACSignal信号对象,这个
// signal信号对象发送从这个block返回的值.
RAC(self.imageView, image) = [[[[client 
	fetchUserWithUsername:@"joshaber"]
	deliverOn:[RACScheduler scheduler]]
	map:^(User *user) {
		// 下载头像(这在后台执行.)
		return [UIImage imageWithData: [NSData dataWithContentsOfURL: user.avatarURL]];
	}]
	// 现在赋值在主线程完成. 
	deliverOn:RACScheduler.mainThreadScheduler];

何时使用 ReactiveCocoa ?

ReactiveCocoa 非常抽象,初次接触,通常很难理解如何使用它来解决具体的问题.

这是一些使用RAC更具有优势的应用场景:

处理异步或事件驱动的数据源.

大多说Cocoa程序的重心在于响应用户事件或程序状态的变化上.处理这些情况的代码,很快就会变得很复杂,就像意大利面条那样,拥有许多的回调和状态变量来处理顺序问题.

一些编程模式,表面上看有些相似,比如 UI回调方法,网络请求的响应和KVO通知等;实际上他们拥有许多的共同点. RACSignal 信号类,统一类这些不同的APIS,以便组合使用和操作它们.

例如,如下代码:


static void *ObservationContext = &ObservationContext;

- (void)viewDidLoad {
	[super viewDidLoad];

	[LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext];
	[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager];

	[self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
	[self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged];
	[self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)dealloc {
	[LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext];
	[NSNotificationCenter.defaultCenter removeObserver:self];
}

- (void)updateLogInButton {
	BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0;
	BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn;
	self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn;
}

- (IBAction)logInPressed:(UIButton *)sender {
	[[LoginManager sharedManager]
		logInWithUsername:self.usernameTextField.text
		password:self.passwordTextField.text
		success:^{
			self.loggedIn = YES;
		} failure:^(NSError *error) {
			[self presentError:error];
		}];
}

- (void)loggedOut:(NSNotification *)notification {
	self.loggedIn = NO;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if (context == ObservationContext) {
		[self updateLogInButton];
	} else {
		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
	}
}

… 可以用RAC这样重写:

- (void)viewDidLoad {
	[super viewDidLoad];

	@weakify(self);

	RAC(self.logInButton, enabled) = [RACSignal
		combineLatest:@[
			self.usernameTextField.rac_textSignal,
			self.passwordTextField.rac_textSignal,
			RACObserve(LoginManager.sharedManager, loggingIn),
			RACObserve(self, loggedIn)
		] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) {
			return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue);
		}];

	[[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) {
		@strongify(self);

		RACSignal *loginSignal = [LoginManager.sharedManager
			logInWithUsername:self.usernameTextField.text
			password:self.passwordTextField.text];

			[loginSignal subscribeError:^(NSError *error) {
				@strongify(self);
				[self presentError:error];
			} completed:^{
				@strongify(self);
				self.loggedIn = YES;
			}];
	}];

	RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter
		rac_addObserverForName:UserDidLogOutNotification object:nil]
		mapReplace:@NO];
}

链式依赖的操作.

依赖关系通常出现在网络请求中,如后一个请求应该等前一个请求完成后再创建,等等:

[client logInWithSuccess:^{
	[client loadCachedMessagesWithSuccess:^(NSArray *messages) {
		[client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) {
			NSLog(@"Fetched all messages.");
		} failure:^(NSError *error) {
			[self presentError:error];
		}];
	} failure:^(NSError *error) {
		[self presentError:error];
	}];
} failure:^(NSError *error) {
	[self presentError:error];
}];

ReactiveCocoa 可以特别方便地处理这种逻辑模式:

[[[[client logIn]
	then:^{
		return [client loadCachedMessages];
	}]
	flattenMap:^(NSArray *messages) {
		return [client fetchMessagesAfterMessage:messages.lastObject];
	}]
	subscribeError:^(NSError *error) {
		[self presentError:error];
	} completed:^{
		NSLog(@"Fetched all messages.");
	}];

并行独立的工作.

使用独立数据的并行工作,然后最终将他们合并到一个结果中,在Cocoa中是很琐碎的,并且常常包含许多同步代码:

__block NSArray *databaseObjects;
__block NSArray *fileContents;
 
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{
	databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate];
}];

NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{
	NSMutableArray *filesInProgress = [NSMutableArray array];
	for (NSString *path in files) {
		[filesInProgress addObject:[NSData dataWithContentsOfFile:path]];
	}

	fileContents = [filesInProgress copy];
}];
 
NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
	[self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents];
	NSLog(@"Done processing");
}];
 
[finishOperation addDependency:databaseOperation];
[finishOperation addDependency:filesOperation];
[backgroundQueue addOperation:databaseOperation];
[backgroundQueue addOperation:filesOperation];
[backgroundQueue addOperation:finishOperation];

以上代码可以通过复合使用signals信号对象来优化:

RACSignal *databaseSignal = [[databaseClient
	fetchObjectsMatchingPredicate:predicate]
	subscribeOn:[RACScheduler scheduler]];

RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
	NSMutableArray *filesInProgress = [NSMutableArray array];
	for (NSString *path in files) {
		[filesInProgress addObject:[NSData dataWithContentsOfFile:path]];
	}

	[subscriber sendNext:[filesInProgress copy]];
	[subscriber sendCompleted];
}];

[[RACSignal
	combineLatest:@[ databaseSignal, fileSignal ]
	reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) {
		[self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents];
		return nil;
	}]
	subscribeCompleted:^{
		NSLog(@"Done processing");
	}];

简化集合的转换.

更高层级的排序函数,比如 map(映射), filter(过滤器), fold(折叠)/reduce(减少),在Foundation 中严重缺失; 这导致必须编写类似于下面的循环代码:

NSMutableArray *results = [NSMutableArray array];
for (NSString *str in strings) {
	if (str.length < 2) {
		continue;
	}

	NSString *newString = [str stringByAppendingString:@"foobar"];
	[results addObject:newString];
}

RACSequence 允许任何Cocoa集合可以使用统一的声明式语法来操作:

RACSequence *results = [[strings.rac_sequence
	filter:^ BOOL (NSString *str) {
		return str.length >= 2;
	}]
	map:^(NSString *str) {
		return [str stringByAppendingString:@"foobar"];
	}];

© 著作权归作者所有

共有 人打赏支持
ios122
粉丝 72
博文 74
码字总数 129462
作品 3
东城
程序员
iOS开发 MVVM-ReactiveCocoa资料

相对好懂一点的: http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-%5B%3F%5D-:xin-hao/ http://southpeak.github.io/blog/2014/08/02/reactivecocoazhi-nan-er-:twitters......

神补刀
2015/11/10
0
0
iOS应用架构谈(二):View层的组织和调用方案(中)

关于MVC、MVVM等一大堆思想 其实这些都是相对通用的思想,万变不离其宗的还是在开篇里面我提到的那三个角色:数据管理者,数据加工者,数据展示者。这些五花八门的思想,不外乎就是制订了一个...

hejunbinlan
2015/08/05
0
0
从 C-41 看 MVVM 和 ReactiveCocoa

从 C-41 看 MVVM 和 ReactiveCocoa 基本概念 C-41 是一个关于 和 的开源程序,我是通过 objc.io 上的一篇文章知道它的,相关地址: 英文版文章 中文版文章 项目地址 () 和 () 都有不错的介绍...

陈圣晗
2015/07/22
0
0
ReactiveCocoa & MVVM 学习总结二

二. MVVM 1. 什么是MVVM From: https://github.com/ReactiveCocoa/ReactiveViewModel#model-view-viewmodel MVVM是指 Model-View-ViewModel的简称,与MVC的一个主要区别是 view 拥有view mo......

lewis-180
2015/09/23
0
0
使用ReactiveCocoa实现iOS平台响应式编程

使用ReactiveCocoa实现iOS平台响应式编程 ReactiveCocoa和响应式编程 在说ReactiveCocoa之前,先要介绍一下FRP(Functional Reactive Programming,响应式编程),在维基百科中有这样一个例子...

陈小猫同学
2014/07/09
0
0
ReactiveCocoa & MVVM 学习总结一

主要是为了总结学习RAC的过程中,遇到的一些困惑点,一些阅读的参考资料,文笔也不是很好。建议大家学习RAC参考文章: https://github.com/ReactiveCocoa/ReactiveCocoa/tree/master/Documen...

lewis-180
2015/09/23
0
0
转【唐巧】的谈谈 React Native

前言 几天前,Facebook 在 React.js Conf 2015 大会上推出了 React Native(视频链接)。我发了一条微博(地址),结果引来了 100 多次转发。为什么 React Native 会引来如此多的关注呢?我在...

山哥
2015/02/02
0
0
iOS开发,一篇写得非常好的入门级ReactiveCocoa教程(一)

ReactiveCocoa入门教程——第一部分 ReactiveCocoa iOS 翻译 2015-01-21 18:33:37 9657 6 15 本文翻译自RayWenderlich ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 作......

神补刀
2015/10/28
0
0
ReactiveCocoa入门教程——第一部分

作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来...

BruceYu的博客
2015/11/10
0
0
iOS开发,一篇写得非常好的入门级ReactiveCocoa教程(二)

ReactiveCocoa入门教程——第二部分 ReactiveCocoa iOS 翻译 2015-05-20 16:37:16 4710 1 1 本文翻译自RayWenderlich ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2 Re......

神补刀
2015/10/28
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周一乱弹 —— 如果是你喜欢的女同学找你借钱

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @guanglun :分享Michael Learns To Rock的单曲《Fairy Tale》 《Fairy Tale》- Michael Learns To Rock 手机党少年们想听歌,请使劲儿戳(这...

小小编辑
29分钟前
7
3
NNS域名系统之域名竞拍

0x00 前言 其实在官方文档中已经对域名竞拍的过程有详细的描述,感兴趣的可以移步http://doc.neons.name/zh_CN/latest/nns_protocol.html#id30 此处查阅。 我这里主要对轻钱包开发中会用到的...

暖冰
今天
0
0
32.filter表案例 nat表应用 (iptables)

10.15 iptables filter表案例 10.16/10.17/10.18 iptables nat表应用 10.15 iptables filter表案例: ~1. 写一个具体的iptables小案例,需求是把80端口、22端口、21 端口放行。但是,22端口我...

王鑫linux
今天
0
0
shell中的函数&shell中的数组&告警系统需求分析

20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析

影夜Linux
今天
0
0
Linux网络基础、Linux防火墙

Linux网络基础 ip addr 命令 :查看网口信息 ifconfig命令:查看网口信息,要比ip addr更明了一些 centos 7默认没安装ifconfig命令,可以使用yum install -y net-tools命令来安装。 ifconfig...

李超小牛子
今天
1
0
[机器学习]回归--Decision Tree Regression

CART决策树又称分类回归树,当数据集的因变量为连续性数值时,该树算法就是一个回归树,可以用叶节点观察的均值作为预测值;当数据集的因变量为离散型数值时,该树算法就是一个分类树,可以很...

wangxuwei
昨天
1
0
Redis做分布式无锁CAS的问题

因为Redis本身是单线程的,具备原子性,所以可以用来做分布式无锁的操作,但会有一点小问题。 public interface OrderService { public String getOrderNo();} public class OrderRe...

算法之名
昨天
11
0
143. Reorder List - LeetCode

Question 143. Reorder List Solution 题目大意:给一个链表,将这个列表分成前后两部分,后半部分反转,再将这两分链表的节点交替连接成一个新的链表 思路 :先将链表分成前后两部分,将后部...

yysue
昨天
1
0
数据结构与算法1

第一个代码,描述一个被称为BankAccount的类,该类模拟了银行中的账户操作。程序建立了一个开户金额,显示金额,存款,取款并显示余额。 主要的知识点联系为类的含义,构造函数,公有和私有。...

沉迷于编程的小菜菜
昨天
1
0
从为什么别的队伍总比你的快说起

在机场候检排队的时候,大多数情况下,别的队伍都要比自己所在的队伍快,并常常懊悔当初怎么没去那个队。 其实,最快的队伍只能有一个,而排队之前并不知道那个队快。所以,如果有六个队伍你...

我是菜鸟我骄傲
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部