ReactiveCocoa - iOS开发的新框架
本文转载至 http://www.infoq.com/cn/articles/reactivecocoa-ios-new-develop-framework
ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程和响应式编程的特性。它主要吸取了.Net的 Reactive Extensions的设计和实现。本文将详细介绍该框架试图解决什么问题,以及其用法与特点。
ReactiveCocoa试图解决什么问题
经过一段时间的研究,我认为ReactiveCocoa试图解决以下3个问题:
- 传统iOS开发过程中,状态以及状态之间依赖过多的问题
- 传统MVC架构的问题:Controller比较复杂,可测试性差
- 提供统一的消息传递机制
传统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 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"); }]; }]; }
如何使用ReactiveCocoa
ReactiveCocoa可以在iOS和OS X的应用开发中使用,对于iOS开发者,可以将RAC源码下载编译后,使用编译好的libReactiveCocoa-iOS.a
文件。
开发者也可以用CocoaPods来设置目标工程对ReactiveCocoa的依赖,只需要编辑Podfile文件,增加如下内容即可:
pod 'ReactiveCocoa', ‘2.0'
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带来了函数式编程和响应式编程的思想,值得大家关注并且学习。
作者简介:
唐巧,资深iOS开发者和Blogger,曾开发有道云笔记、猿题库和粉笔网的iOS客户端。他维护着iOS开发博客 http://www.devtang.com/ 和微信 iOS开发公众账号 iosDevTips。唐巧在2014年QCon北京将进行一场iOS开发进阶的培训,介绍如何基于CoreText自己实现一个排版引擎。
ReactiveCocoa - iOS开发的新框架的更多相关文章
- iOS 开发之照片框架详解
转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework.html 一. 概要 在 iOS 设备中,照片和视频是相当重 ...
- iOS 开发之照片框架详解(2)
一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLib ...
- iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)
本文链接:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-three.html 这里接着前文<iOS ...
- iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)
转载自:http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html 一. 概况 本文接着 iOS 开 ...
- IOS开发之新浪围脖
IOS开发和Web开发一样,网络请求方式包括Get和Post方式.Get和Post两者有和特点和区别,在本篇博客中不做过多的论述,本篇的重点在于如何GET数据和POST数据.下面还会提到如何在我们的项 ...
- iOS 开发之照片框架详解(1)
http://kayosite.com/ios-development-and-detail-of-photo-framework.html/comment-page-1 一. 概要 在 iOS 设备 ...
- 【iOS开发】Alamofire框架的使用二 高级用法
Alamofire是在URLSession和URL加载系统的基础上写的.所以,为了更好地学习这个框架,建议先熟悉下列几个底层网络协议栈: URL Loading System Programming ...
- iOS开发--SQLite重要框架FMDB的使用
什么是FMDB: FMDB是一个和iOS的SQLite数据库操作相关的第三方框架.主要把C语言操作数据库的代码用OC进行了封装.使用者只需调用该框架的API就能用来创建并连接数据库,创建表,查询等. ...
- 【iOS开发】Alamofire框架的使用一基本用法
Alamofire框架的使用一 —— 基本用法 对于使用Objective-C的开发者,一定非常熟悉AFNetworking这个网络框架.在苹果推出的Swift之后,AFNetworking的作者专门 ...
随机推荐
- redis未授权
redis描述: Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.从2010年3月15日起,Redis的开发工 ...
- python 调用函数 / 类型转换 / 切片/ 迭代
调用函数 / 类型转换 / 切片/ 迭代 1. 调用函数:abs(),max(),min() 2. 数据类型转换:int(),float(),str(),tool(),a=abs, 3. 定义函数, ...
- hive 定时加载分区
#!/bin/bash #每天定时位外部表加载分区 #服务器当天的时间 #加载环境变量 source /etc/profile; #如果没有指定日期用当前日期如果指定的日期使用指定的日期 echo ' ...
- java日期工具类DateUtil
一名优秀的程序员,不仅需要有着丰富解决问题的方案,还需要的便是代码的沉淀,这不仅有助于自己快速的开发程序,也有利于保证程序的健壮.那如何才能沉淀自己的”代码“呢?从自己编写util开始其实就是一个不错 ...
- size_t ssize_t socklen_t
size_t 解释一:为了增强程序的可移植性,便有了size_t,它是为了方便系统之间的移植而定义的,不同的系统上,定义size_t可能不一样. 在32位系统上 定义为 unsigned int 也就 ...
- iio adc转换应用编写
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> # ...
- Spring引入配置文件
1.spring.xml加载映射的配置配置文件 <!--采用这种方式简化配置文件--> <context:property-placeholder location="cl ...
- win7怎么把软件加入开机启动项
点击开机菜单进入. 2 然后点击菜单栏中的所有程序. 3 在所有程序中找到‘启动’,这个启动就是开机启动项来的. 4 右键进行打开启动项. 5 把快捷方式复制到开机启动项中.如果找不到快捷方式可以右键 ...
- python + opencv: kalman 跟踪
之前博文中讲解过kalman滤波的原理和应用,这里用一个跟踪鼠标的例程来演示怎么在opencv里用自带的kalman函数进行目标跟踪,文章的内容对做图像跟踪有借鉴意义.文章主要是网络资源进行整理和简单 ...
- 第三百零四节,Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器
Django框架,urls.py模块,views.py模块,路由映射与路由分发以及逻辑处理——url控制器 这一节主讲url控制器 一.urls.py模块 这个模块是配置路由映射的模块,当用户访问一个 ...