什么是 ReactiveCocoa

ReactiveCocoa(其简称为 RAC)是由 Github 开源的一个应用于 iOS 和 OS X 开发的新框架。RAC 具有函数式编程和响应式编程的特性。它主要吸取了 .Net 的 Reactive Extensions 的设计和实现。

ReactiveCocoa 试图解决什么问题

经过一段时间的研究,我认为 ReactiveCocoa 试图解决以下 3 个问题:

  1. 传统 iOS 开发过程中,状态以及状态之间依赖过多的问题
  2. 传统 MVC 架构的问题:Controller 比较复杂,可测试性差
  3. 提供统一的消息传递机制

传统 iOS 开发过程中,状态以及状态之间依赖过多的问题

我们在开发 iOS 应用时,一个界面元素的状态很可能受多个其它界面元素或后台状态的影响。

例如,在用户帐户的登录界面,通常会有 2 个输入框(分别输入帐号和密码)和一个登录按钮。如果我们要加入一个限制条件:当用户输入完帐号和密码,并且登录的网络请求还未发出时,确定按钮才可以点击。通常情况下,我们需要监听这两个输入框的状态变化以及登录的网络请求状态,然后修改另一个控件的enabled状态。

传统的写法如下(该示例代码修改自 ReactiveCocoa 官网 ) :

static void *ObservationContext = &ObservationContext;

- (void)viewDidLoad {
[super viewDidLoad]; [LoginManager.sharedManager addObserver:self
forKeyPath:@"loggingIn"
options:NSKeyValueObservingOptionInitial
context:&ObservationContext];
[self.usernameTextField addTarget:self action:@selector(updateLogInButton)
forControlEvents:UIControlEventEditingChanged];
[self.passwordTextField addTarget:self action:@selector(updateLogInButton)
forControlEvents:UIControlEventEditingChanged];
} - (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;
} - (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 通过引入信号(Signal)的概念,来代替传统 iOS 开发中对于控件状态变化检查的代理(delegate)模式或 target-action 模式。因为 RAC 的信号是可以组合(combine)的,所以可以轻松地构造出另一个新的信号出来,然后将按钮的enabled状态与新的信号绑定。如下所示:

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);
}];

可以看到,在引入 RAC 之后,以前散落在action-target或 KVO 的回调函数中的判断逻辑被统一到了一起,从而使得登录按钮的enabled状态被更加清晰地表达了出来。

除了组合(combine)之外,RAC 的信号还支持链式(chaining)和过滤(filter),以方便将信号进行进一步处理。

试图解决 MVC 框架的问题

对于传统的 Model-View-Controller 的框架,Controller 很容易变得比较庞大和复杂。由于 Controller 承担了 Model 和 View 之间的桥梁作用,所以 Controller 常常与对应的 View 和 Model 的耦合度非常高,这同时也造成对其做单元测试非常不容易,对 iOS 工程的单元测试大多都只在一些工具类或与界面无关的逻辑类中进行。

RAC 的信号机制很容易将某一个 Model 变量的变化与界面关联,所以非常容易应用 Model-View-ViewModel 框架。通过引入 ViewModel 层,然后用 RAC 将 ViewModel 与 View 关联,View 层的变化可以直接响应 ViewModel 层的变化,这使得 Controller 变得更加简单,由于 View 不再与 Model 绑定,也增加了 View 的可重用性。

因为引入了 ViewModel 层,所以单元测试可以在 ViewModel 层进行,iOS 工程的可测试性也大大增强了。InfoQ 也曾撰文介绍过 MVVM:《MVVM 启示录》 。

统一消息传递机制

iOS 开发中有着各种消息传递机制,包括 KVO、Notification、delegation、block 以及 target-action 方式。各种消息传递机制使得开发者在做具体选择时感到困惑,例如在 objc.io 上就有 专门撰文破船的翻译 ),介绍各种消息传递机制之间的差异性。

RAC 将传统的 UI 控件事件进行了封装,使得以上各种消息传递机制都可以用 RAC 来完成。示例代码如下:

// KVO
[RACObserve(self, username) subscribeNext:^(id x) {
NSLog(@" 成员变量 username 被修改成了:%@", x);
}]; // target-action
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@" 按钮被点击 ");
return [RACSignal empty];
}]; // Notification
[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:UIKeyboardDidChangeFrameNotification
object:nil]
subscribeNext:^(id x) {
NSLog(@" 键盘 Frame 改变 ");
}
]; // Delegate
[[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) {
debugLog(@"viewWillAppear 方法被调用 %@", x);
}];

RAC 的RACSignal 类也提供了createSignal方法来让用户创建自定义的信号,如下代码创建了一个下载指定网站内容的信号。

-(RACSignal *)urlResults {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSError *error;
NSString *result = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"http://www.devtang.com"]
encoding:NSUTF8StringEncoding
error:&error];
NSLog(@"download");
if (!result) {
[subscriber sendError:error];
} else {
[subscriber sendNext:result];
[subscriber sendCompleted];
}
return [RACDisposable disposableWithBlock:^{
NSLog(@"clean up");
}];
}]; }

如何使用 ReactiveCocoa

ReactiveCocoa 可以在 iOS 和 OS X 的应用开发中使用,对于 iOS 开发者,可以将 RAC 源码 下载编译后,使用编译好的libReactiveCocoa-iOS.a文件。

开发者也可以用 CocoaPods 来设置目标工程对 ReactiveCocoa 的依赖,只需要编辑 Podfile 文件,增加如下内容即可:

pod 'ReactiveCocoa'

ReactiveCocoa 的特点

RAC 在应用中大量使用了 block,由于 Objective-C 语言的内存管理是基于 引用计数 的,为了避免循环引用问题,在 block 中如果要引用 self,需要使用@weakify(self)@strongify(self)来避免强引用。另外,在使用时应该注意 block 的嵌套层数,不恰当的滥用多层嵌套 block 可能给程序的可维护性带来灾难。

RAC 的编程方式和传统的 MVC 方式差异巨大,所以需要较长的学习时间。并且,业界内对于 RAC 并没有广泛应用,这造成可供参考的项目和教程比较欠缺。
另外,RAC 项目本身也还在快速演进当中,1.x 版本和 2.x 版本 API 改动了许多,3.0 版本也正在快速开发中,对它的使用也需要考虑后期的升级维护问题。

作为一个 iOS 开发领域的新开源框架,ReactiveCocoa 带来了函数式编程和响应式编程的思想,值得大家关注并且学习。

一些学习资源

博客 & 教程

代码

书籍

视频

RAC(ReactiveCocoa)的更多相关文章

  1. radhat6.6上安装oracle12c RAC (三)

    三.DB(database)安装 3.1 DB软件安装 orale用户登录,解压db安装包 [root@jydb1 tools]# su - oracle [oracle@jydb1 ~]$ cd / ...

  2. radhat6.6上安装oracle12c RAC (一)

    软件环境:VMware.redhat6.6.oracle12c(linuxx64_12201_database.zip).12cgrid(linuxx64_12201_grid_home.zip) 一 ...

  3. radhat6.6上安装oracle12c RAC (二)

    二.GI(Grid Infrastructure)安装 首先将安装包传到node1的目录 2.1 GI软件安装 2.1.1.解压安装包 #su - grid解压 GRID 到 GRID用户的$ORAC ...

  4. 11.2.0.3 RAC(VCS)节点crash以及hang的问题分析

    昨天某个客户的一套双节RAC当中一个节点crash,同一时候最后导致另外一个节点也hang住,仅仅能shutdown abort. 且出现shutdown abort实例之后,还有部分进程无法通过ki ...

  5. RAC_Oracle集群服务安装RAC(案例)

    2015-01-23 Created By BaoXinjian Thanks and Regards

  6. Oracle RAC(Real Application Clusters)とは

    ここからはOracle RAC(Real Application Clusters)の基本的な概念について述べていきたいと思います. 「RAC」の読み方は普通に「ラック」です. その前に.通常のデータ ...

  7. ReactiveCocoa 入门学习 (一)

    引言 现在由于需求的不断发展,MVC这个经典的框架由于Controller的任务越来越多,显得"臃肿"了,网上又推出了新的框架,比如MVVM,ReactiveCocoa, 今天就来 ...

  8. ReactiveCocoa应用篇(二)

    上一篇介绍了ReactiveCocoa的常用类,已经基本满足项目中的简单应用要求,但是针对复杂的功能还需要其它的类来协同处理.ReactiveCocoa提供了强大的流程处理功能来解决复杂的问题,包括事 ...

  9. 函数响应式编程(FRP)框架--ReactiveCocoa

    由于工作原因,有段时间没更新博客了,甚是抱歉,只是,从今天開始我又活跃起来了,哈哈,于是决定每周更新一博.大家互相学习.交流. 今天呢.讨论一下关于ReactiveCocoa,这个採用函数响应式编程( ...

随机推荐

  1. 快速玩转Apple Pay开发

    快速玩转Apple Pay开发 更新时间:2016年02月20日09时25分 来源:传智播客 Apple Pay 2016年2月18日上午,苹果公司宣布,与中国银联达成合作,正式在中国大陆上线Appl ...

  2. 自定义switch开关

    自定义一个switch开关 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  3. 8.2 sikuli 集成进eclipse 报错:Getting the VisionProxy.dll: Can not find dependent libraries...

    如果在执行脚本的时候出现以下错误: Getting the VisionProxy.dll: Can not find dependent libraries... 把Sikuli X 的libs目录 ...

  4. 用GeneratedKeyHolder获得新建数据主键值

    public User createUser(final User user) { final String sql = "insert into sys_users(username, p ...

  5. HTML+CSS D08浮动

    1. <html> <head> <title>div浮动</title> <style type="text/css"> ...

  6. Android OpenGL ES(九)绘制线段Line Segment .

    创建一个DrawLine Activity,定义四个顶点: float vertexArray[] = { -0.8f, -0.4f * 1.732f, 0.0f, -0.4f, 0.4f * 1.7 ...

  7. NSAttributedString in Swift

    转载自: https://www.invasivecode.com/weblog/attributed-text-swift/   I have been talking quite a lot in ...

  8. UITextField和一个UILabel绑定 浅析

    转载自:http://fengdeng.github.io/blog/2016/01/22/rxswift-dao-di-%5B%3F%5D-ge-uitextfieldshi-ru-he-he-%5 ...

  9. Loadrunner性能测试分类详(二)

    一.基准测试 有基础的标准,这样能通过对比发现系统的不同点与变化. 1.可以再指定的标准下通过基准测试建立一个性能基准,这样以后当系统的环境.参数发生变化后,再进行一次相同标准下的测试,即可看出变化对 ...

  10. HDU 4685 Prince and Princess(二分匹配+强联通分量)

    题意:婚配问题,但是题目并不要求输出最大匹配值,而是让我们输出,一个王子可以与哪些王妃婚配而不影响最大匹配值. 解决办法:先求一次最大匹配,如果有两个已经匹配的王妃,喜欢她们两个的有两个或者以上相同的 ...