ReactiveCocoa 初見

閑話: 聽說學(xué) Haskell 可以打開新世界的大門

剛剛發(fā)現(xiàn) ReactiveCocoa 的時候,看到相關(guān)的術(shù)語 signal、subscriber 之類的,不明覺厲。再加上 FRP - Functional Reactive Programming,我似乎看到了新世界的大門。

作為初見,希望盡可能地提取關(guān)鍵概念來理解這個非常熱門但是有些難懂的框架,減少打開新世界大門的阻力。

<br /><br />

編程范式 Programming Paradigm

Wikipedia 上搜索這個關(guān)鍵詞的話,就可以看到在這個詞條右邊列出了幾十個編程范式,領(lǐng)略一下前人的腦洞。<br />
( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///)

咳咳,對于 ReactiveCocoa 這個框架最先應(yīng)該了解的是 Functional ProgrammingReactive Programming

函數(shù)式編程 Functional Programming

參考 Wikipedia 我的理解是:

  • 函數(shù)可以作為參數(shù)傳遞
  • 組合各種函數(shù)來實現(xiàn)所需

還有純函數(shù)式編程語言里沒有變量之類的暫時不去深究。這個范式其實在現(xiàn)代編程語言中大多都支持。

響應(yīng)式編程 Reactive Programming

重點就一個,數(shù)據(jù)可以隨著事件動態(tài)變化,就如同 Wiki 中所說的,表達(dá)式的結(jié)果會因為表達(dá)式中的變量改變自動更新。

那么上面兩種范式結(jié)合之后是什么?

函數(shù)響應(yīng)式編程 Functional Reactive Programming

重點還是在響應(yīng),通過組合函數(shù)可以實現(xiàn)復(fù)雜的響應(yīng)過程。

希望詳細(xì)了解,這里 有一篇很好的關(guān)于 FRP 的文章

<br />

MVC vs MVVM

iOS 開發(fā)過程中會遇到在一個 View Controller 的文件里,有著幾百上千行的代碼。View Controller 總是承擔(dān)著過多的任務(wù),這里 MVVM 的出現(xiàn)就是為了剝離 View Controller 中過多的代碼,objc.io 的第一個 issue 就是一篇分離 View Controller 和 Table View 的很好的文章,英文版,中文版。

另外 這里 有一篇很好的用 ReactiveCocoa 實現(xiàn) MVVM 的文章,重點是輕量化 ViewController,組合兩種架構(gòu),不是完全替換 MVC。

<br />

Reactive Cocoa

終于進入正題。這個框架,就是吸收了如同 Haskell 這類函數(shù)式語言的思想,從微軟的 Rx 演化來的。利用它就可以很好的實現(xiàn) MVVM 的架構(gòu),防止臃腫雜亂的 View Controller。

使用它會進入完全不同的另一種編程思維,用這另一種思維去看以前遇到的問題,就看到新世界的大門了<br />
(??????)??

RAYWENDERLICH 上的 一篇文章 很詳細(xì)的介紹了框架基本的用法。不過無論是這篇還是上一節(jié)的那篇文章,篇幅都很長,根據(jù)本文的初衷,下面總結(jié)一下。

信號 Signal

這個就是最核心的概念,操作都基于對信號的各種處理上。信號就是用來 承載 數(shù)據(jù)的,跟滿天飛來飛去的無線電波一樣。我們可以對信號做各種處理,像是監(jiān)聽、過濾等等,在 Reactive Cocoa 中的信號非常類似于人們自然理解的信號。

操作符 Operator

處理信號使用操作符,代碼上其實就是一個參數(shù)是 block 的函數(shù)。block 里面就是怎么處理信號。框架里面提供了很多操作符,參看 github 上的 文檔。

<br />

應(yīng)用內(nèi)切換語言

如果已經(jīng)看過之前提到的兩篇教程文章,相比那接下來的示例會更簡單。之前兩篇文章都是用了搜索 Twitter 的推文來展示框架的用法,鑒于你懂得的原因,和新浪微博的接口復(fù)雜一些,搜索功能也很限制,所以我用了這個想到就會覺得實現(xiàn)起來會很麻煩的功能。

產(chǎn)品的國際化就像牙線:所有人都知道他們應(yīng)該使用,卻可能都不去用。 -- 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.");
}];

在任何需要國際化的地方只要這么寫就可以,其實國際化就是一勞永逸的工作,習(xí)慣之后其實非常簡單。

@weakify(self)@strongify(self) 是用來方便地解決循環(huán)引用的,需要另外包含頭文件 #import "EXTScope.h"。

subscribeNext: 方法是訂閱信號,會在信號發(fā)送 sendNext 時執(zhí)行 block 內(nèi)的代碼,這里就是刷新 UI

下面是如何發(fā)送信號

創(chuàng)建語言管理(視圖模型)類

我們整個的信號流程很簡單:修改語言(變化產(chǎn)生數(shù)據(jù)流) --> 加載語言文件 --> 刷新 UI

首先呢,不得不放棄 NSLocalizedString 的方法,我還沒找到可以直接修改地區(qū)的方法。

創(chuàng)建一個語言管理的單例類

@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: 方法獲取當(dāng)前語言的字符串<br />
languages 方法返回所有支持的語言列表,在語言選擇的 Table View 里使用<br />
changeLanguageTo: 方法用來變更當(dāng)前語言<br />
另外下面添加了幾個使代碼整潔的宏。

我們只需要關(guān)注 languageChangedSignal 的實現(xiàn)

- (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;
}

這段代碼看起來比較亂,只需要關(guān)注 RACObserve 這個框架提供的宏和 doNext: 方法。

RACObserve(self, currentLanguage) 就是創(chuàng)建了 currentLanguage 這個屬性的變化的信號。<br />
doNext:^(NSString *currentLanguage) {...} 這個方法是在更改 UI 之前插入需要執(zhí)行的動作。block 中略長的內(nèi)容是根據(jù)語言代碼獲取對應(yīng)的 strings 文件。

Reactive Cocoa 是基于 KVO 的,所以要注意觀察的屬性是不是支持 KVO。在這里就是如果你使用下劃線的熟悉去修改,就不會發(fā)生任何你想要的事,需要使用 setter 的方式去修改(self.currentLanguage)。

接下來

其實沒有接下來了,沒錯,就是這么簡單,核心部分的代碼就是這樣。

其他細(xì)節(jié)實現(xiàn)可以 clone 或下載本示例項目 RAC-International-Example

<br />

新世界的大門

新世界的大門打開了,使用 Reactive Cocoa 確實讓代碼變得很不同,雖然很多陌生的概念,但是當(dāng)你熟悉和深入了解之后,他就是進入新世界的鑰匙。

<br />

附錄

  1. Github Repo - ReactiveCocoa
  2. Wikipedia - Functional Programming
  3. Wikipedia - Reactive Programming
  4. 函數(shù)式反應(yīng)型編程(FRP) —— 實時互動應(yīng)用開發(fā)的新思路
  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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容