ReactiveCocoa 初见

闲话: 听说学 Haskell 可以打开新世界的大门

刚刚发现 ReactiveCocoa 的时候,看到相关的术语 signalsubscriber 之类的,不明觉厉。再加上 FRP - Functional Reactive Programming,我似乎看到了新世界的大门。

作为初见,希望尽可能地提取关键概念来理解这个非常热门但是有些难懂的框架,减少打开新世界大门的阻力。

<br /><br />

编程范式 Programming Paradigm

Wikipedia 上搜索这个关键词的话,就可以看到在这个词条右边列出了几十个编程范式,领略一下前人的脑洞。<br />
( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///)

咳咳,对于 ReactiveCocoa 这个框架最先应该了解的是 Functional ProgrammingReactive Programming

函数式编程 Functional Programming

参考 Wikipedia 我的理解是:

  • 函数可以作为参数传递
  • 组合各种函数来实现所需

还有纯函数式编程语言里没有变量之类的暂时不去深究。这个范式其实在现代编程语言中大多都支持。

响应式编程 Reactive Programming

重点就一个,数据可以随着事件动态变化,就如同 Wiki 中所说的,表达式的结果会因为表达式中的变量改变自动更新。

那么上面两种范式结合之后是什么?

函数响应式编程 Functional Reactive Programming

重点还是在响应,通过组合函数可以实现复杂的响应过程。

希望详细了解,这里 有一篇很好的关于 FRP 的文章

<br />

MVC vs MVVM

iOS 开发过程中会遇到在一个 View Controller 的文件里,有着几百上千行的代码。View Controller 总是承担着过多的任务,这里 MVVM 的出现就是为了剥离 View Controller 中过多的代码,objc.io 的第一个 issue 就是一篇分离 View Controller 和 Table View 的很好的文章,英文版中文版

另外 这里 有一篇很好的用 ReactiveCocoa 实现 MVVM 的文章,重点是轻量化 ViewController,组合两种架构,不是完全替换 MVC。

<br />

Reactive Cocoa

终于进入正题。这个框架,就是吸收了如同 Haskell 这类函数式语言的思想,从微软的 Rx 演化来的。利用它就可以很好的实现 MVVM 的架构,防止臃肿杂乱的 View Controller。

使用它会进入完全不同的另一种编程思维,用这另一种思维去看以前遇到的问题,就看到新世界的大门了<br />
(๑•̀ㅂ•́)و✧

RAYWENDERLICH 上的 一篇文章 很详细的介绍了框架基本的用法。不过无论是这篇还是上一节的那篇文章,篇幅都很长,根据本文的初衷,下面总结一下。

信号 Signal

这个就是最核心的概念,操作都基于对信号的各种处理上。信号就是用来 承载 数据的,跟满天飞来飞去的无线电波一样。我们可以对信号做各种处理,像是监听、过滤等等,在 Reactive Cocoa 中的信号非常类似于人们自然理解的信号。

操作符 Operator

处理信号使用操作符,代码上其实就是一个参数是 block 的函数。block 里面就是怎么处理信号。框架里面提供了很多操作符,参看 github 上的 文档

<br />

应用内切换语言

如果已经看过之前提到的两篇教程文章,相比那接下来的示例会更简单。之前两篇文章都是用了搜索 Twitter 的推文来展示框架的用法,鉴于你懂得的原因,和新浪微博的接口复杂一些,搜索功能也很限制,所以我用了这个想到就会觉得实现起来会很麻烦的功能。

产品的国际化就像牙线:所有人都知道他们应该使用,却可能都不去用。 -- NSHipster

这个例子的完整代码可以在 github 上找到 RAC-International-Example

首先我们先看一下成果

RAC-International-Example.gif

最终在我们需要国际化的地方的代码长这样

@weakify(self);
[LanguageChangedSignal subscribeNext:^(NSString *languageCode) {
    @strongify(self);
    self.languageButton.title = LocalizedString(@"Language");
    self.titleLabel.text = LocalizedString(@"Hello World");
    [self.button setTitle:LocalizedString(@"Button") forState:UIControlStateNormal];
    self.label.text = LocalizedString(@"Label");
    self.textView.text = LocalizedString(@"It's a pretty day.");
}];

在任何需要国际化的地方只要这么写就可以,其实国际化就是一劳永逸的工作,习惯之后其实非常简单。

@weakify(self)@strongify(self) 是用来方便地解决循环引用的,需要另外包含头文件 #import "EXTScope.h"

subscribeNext: 方法是订阅信号,会在信号发送 sendNext 时执行 block 内的代码,这里就是刷新 UI

下面是如何发送信号

创建语言管理(视图模型)类

我们整个的信号流程很简单:修改语言(变化产生数据流) --> 加载语言文件 --> 刷新 UI

首先呢,不得不放弃 NSLocalizedString 的方法,我还没找到可以直接修改地区的方法。

创建一个语言管理的单例类

@class RACSignal;

@interface LanguageManager : NSObject

+ (LanguageManager *)shareInstance;

- (RACSignal *)languageChangedSignal;
- (NSString *)localizedString:(NSString *)key;

- (NSArray *)languages;
- (void)changeLanguageTo:(NSString *)language;

@end

#define LanguageViewModel [LanguageManager shareInstance]
#define LocalizedString(key) [LanguageViewModel localizedString:(key)]
#define LanguageChangedSignal [LanguageViewModel languageChangedSignal]

languageChangedSignal 方法返回了一个语言变化的信号,用来给全局需要变化的地方订阅用<br />
localizedString: 方法获取当前语言的字符串<br />
languages 方法返回所有支持的语言列表,在语言选择的 Table View 里使用<br />
changeLanguageTo: 方法用来变更当前语言<br />
另外下面添加了几个使代码整洁的宏。

我们只需要关注 languageChangedSignal 的实现

- (RACSignal *)languageChangedSignal {
    if (!_languageChangedSignal) {
        @weakify(self);
        self.languageChangedSignal = [RACObserve(self, currentLanguage) doNext:^(NSString *currentLanguage) {
            @strongify(self);
            [[NSUserDefaults standardUserDefaults] setObject:currentLanguage forKey:@"currentLanguage"];
            NSBundle *localizeBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:self.currentLanguage ofType:@"lproj"]];
            self.stringsFile = [[NSDictionary alloc] initWithContentsOfFile:[localizeBundle pathForResource:LocalizationFile ofType:@"strings"]];
            if (!self.stringsFile) {
                NSBundle *baseBunble = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"Base" ofType:@"lproj"]];
                self.stringsFile = [[NSDictionary alloc] initWithContentsOfFile:[baseBunble pathForResource:LocalizationFile ofType:@"strings"]];
            }
        }];
    }
    return _languageChangedSignal;
}

这段代码看起来比较乱,只需要关注 RACObserve 这个框架提供的宏和 doNext: 方法。

RACObserve(self, currentLanguage) 就是创建了 currentLanguage 这个属性的变化的信号。<br />
doNext:^(NSString *currentLanguage) {...} 这个方法是在更改 UI 之前插入需要执行的动作。block 中略长的内容是根据语言代码获取对应的 strings 文件。

Reactive Cocoa 是基于 KVO 的,所以要注意观察的属性是不是支持 KVO。在这里就是如果你使用下划线的熟悉去修改,就不会发生任何你想要的事,需要使用 setter 的方式去修改(self.currentLanguage)。

接下来

其实没有接下来了,没错,就是这么简单,核心部分的代码就是这样。

其他细节实现可以 clone 或下载本示例项目 RAC-International-Example

<br />

新世界的大门

新世界的大门打开了,使用 Reactive Cocoa 确实让代码变得很不同,虽然很多陌生的概念,但是当你熟悉和深入了解之后,他就是进入新世界的钥匙。

<br />

附录

  1. Github Repo - ReactiveCocoa
  2. Wikipedia - Functional Programming
  3. Wikipedia - Reactive Programming
  4. 函数式反应型编程(FRP) —— 实时互动应用开发的新思路
  5. objc.io - Lighter View Controllers
  6. Sprynthesis - ReactiveCocoa and MVVM, an Introduction
  7. RayWenderlich ReactiveCocoa Tutorial – The Definitive Introduction
  8. Github - ReactiveCocoa Documentation
  9. NSHipster - NSLocale
  10. 示例代码 - RAC-International-Example
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容