ReactiveCocoa 框架的基本了解
ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程和响应式编程的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。本文将详细介绍该框架试图解决什么问题,以及其用法与特点。
函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
响应式编程:(Reactive Programming):关注于数据流和变化传播。
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP)框架。
ReactiveCocoa试图解决什么问题
经过一段时间的研究,我认为ReactiveCocoa试图解决以下个问题:
- 传统iOS开发过程中,状态以及状态之间依赖过多的问题
- 传统MVC架构的问题:Controller比较复杂,可测试性差
- 提供统一的消息传递机制
传统iOS开发过程中,状态以及状态之间依赖过多的问题
我们在开发iOS应用时,一个界面元素的状态很可能受多个其它界面元素或后台状态的影响。
例如,在用户帐户的登录界面,通常会有2个输入框(分别输入帐号和密码)和一个登录按钮。如果我们要加入一个限制条件:当用户输入完帐号和密码,并且登录的网络请求还未发出时,确定按钮才可以点击。通常情况下,我们需要监听这两个输入框的状态变化以及登录的网络请求状态,然后修改另一个控件的enabled状态。
static void *ObservationContext = &ObservationContext;
(void)viewDidLoad {
[super viewDidLoad];
// 这里是使用 kvo 的方式来监听 LoginManager.sharedManager 的 loggingIn 这个属性
[LoginManager.sharedManager addObserver:self
forKeyPath:@"loggingIn"
options:NSKeyValueObservingOptionInitial
context:&ObservationContext];
// 这里是添加监听者 来监听 usernameTextField 和 passwordTextField 值的改变,当值改变的时候调用 updateLogInButton 这个方法。
[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;
}
// 这个是kvo 调用的方法。
- (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状态与新的信号绑定。
/*
combineLatest : 是组合信号(组合了4个信号)
1).self.usernameTextField.rac_textSignal( 这个是分类的方式实现的)
2).self.passwordTextField.rac_textSignal(同上)
3).RACObserve(LoginManager.sharedManager, loggingIn)
4).RACObserve(self, loggedIn)
reduce:简化信号
self.usernameTextField.rac_textSignal 对应 username
self.passwordTextField.rac_textSignal 对应 password
RACObserve(LoginManager.sharedManager, loggingIn) 对应 loggingIn
RACObserve(self, loggedIn) 对应 loggedIn
这个方法调用返回的结果还是一个信号:
RAC(self.logInButton, enabled) 这是一个 RAC 的宏。意思是通过返回的信号来修改 self.logInButton 的状态是否可用。
*/
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 的理由 **
RAC的信号机制很容易将某一个Model变量的变化与界面关联,所以非常容易应用Model-View-ViewModel 框架。通过引入ViewModel层,然后用RAC将ViewModel与View关联,View层的变化可以直接响应ViewModel层的变化,这使得Controller变得更加简单,由于View不再与Model绑定,也增加了View的可重用性。
因为引入了ViewModel层,所以单元测试可以在ViewModel层进行,iOS工程的可测试性也大大增强了。
RAC 中统一的消息传递机制
iOS开发中有着各种消息传递机制,包括KVO、Notification、delegation、block以及target-action方式。** ReactiveCocoa为事件定义了一个标准接口,从而可以使用一些基本工具来更容易的连接、过滤和组合。**
各种消息传递机制使得开发者在做具体选择时感到困惑,例如在objc.io上就有专门撰文(破船的翻译 ),介绍各种消息传递机制之间的差异性。
** RAC 统一消息传递的示例 **
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 addObserver:self
selector:@selector(keyboardDidChangeFrameNotificationHandler:)
name:UIKeyboardDidChangeFrameNotification object:nil];
RAC的RACSignal 类也提供了createSignal方法来让用户创建自定义的信号,如下代码创建了一个下载指定网站内容的信号。
(RACSignal *)urlResults {
return [RACSignal createSignal:^RACDisposable *(id 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");
}];
}];
}
网友评论