Reactive Cocoa Tutorial 系列,轉載請注明該文源地址 -- by sunny
在RAC下開發干的最多的事就是建立RACSignal和subscribe RACSignal了,它是RAC的核心所在。本篇介紹了RAC的運作原理和設計思路,從函數式編程形成的RACStream繼而介紹它的子類 - RAC最核心的部分RACSignal。
我們知道Reactive Cocoa是函數式編程(Functional PRograming)(FP)思想的實現。FP有一套成熟的理論,這里只講講我個人理解吧。
我覺得FP就是“像計算函數表達式一樣來解決一個問題”,舉個栗子,中學題:
已知:f(x) = 2sin(x + π/2), 求 f(π/2)的值。
其中x是這個函數的輸入,f(x)為計算的輸出結果,求f(π/2)時給定了x自然能計算出個結果來(說實話我真忘了咋算了)
當然,仔細看這個函數,其實是可以分解成幾個小函數的:
f1(x) = x + π/2f2(x) = sin(x)f3(x) = 2x
而原來的f(x)可以被小函數組合:
f(x) = f3(f2(f1(x)))
所以不難得出這么個推論:要是我手上有足夠的基本函數,我就能用上面的組合的方法組合出任意一個復雜的函數了。再想想事實上這些年來學數學的過程不就是在一個個積累基本函數的過程嘛,從基本運算,到三角函數,到乘方開方,再到微積分?;竞瘮翟絹碓蕉啵芙鉀Q的數學問題也越來越復雜。
再來看一個函數是怎么構成的,FP理論里叫monads,十分抽象,沒讀懂,但能理解出來:一個函數只要有一個對于輸入值的運算方法和一個返回值,就夠了。也容易理解,給它一個輸入,干點事情,給出一個輸出,就行了,當然現實情況要復雜得多(比如說輸出值本身就是個函數?)有些函數是有輸入的條件的,比如原來數學解個函數時候經常跟個作用域或者限制條件,比如f(x) = 10 / x , (x不為0),要是傳個0這個函數就認為計算錯誤。
對于像上面栗子的函數,每個函數都能接收上一個函數輸出的結果,作為自己的輸入,這樣才能嵌套生成最終結果,同時,計算的順序也是一定從里向外,所以換個寫法可以寫成:
start ---x--> f1(x) --(temp value1)--> f2(temp value1) --(temp value2)--> f3(temp value2) ---> result
于是乎嵌套就被表示成了序列,來個高大上的名字怎么樣,就叫流(Stream)
這就是RACStream所表示的含義。
按照上面說的,其實RACStream的名字有點點歧義,對于一個RACStream對象,它在意義上等同于上面的f1(x),f2(x),f3(x),而不是那一大串整體,表示整體的應該是最外層的和f(x)對應的那個對象,叫個RACStreamComponent比較好?理解時候得注意下。
所以作為一個基本函數的RACStream應該至少應該有:
得益于在Objc下實現,所以輸入輸出的“值”都用個id類型就行了,遇到多個值的組合就用RACTurple(可以把多個值壓包和解包,類比WINRAR),1和2解決
RACStream從實例變量來看只有一個name,當然它也只應該有個name - -,5解決
里面重點問題就是上面的3和4了。由于函數組合之后仍然是個函數,所以也很容易理解兩個Stream對象的組合其實就是生成一個新的Stream對象,它返回了分別由兩個子Stream先后運算產生的最終結果
觀摩一下RACStream定義的基本方法:
+ (instancetype)empty;+ (instancetype)return:(id)value;- (instancetype)bind:(RACStreamBindBlock (^)(void))block; // for 4- (instancetype)concat:(RACStream *)stream; // for 3- (instancetype)zipWith:(RACStream *)stream; // for 3
RACStream作為一個描述抽象的父類,這幾個基本方法并沒有實現,是由具體子類來實現,RACStream的兩個子類分別是RACSignal和RACSequence
+empty 是一個不返回值,立刻結束(Completed)的函數,意思是執行它之后除了立刻結束啥都不會發生,可以理解為RAC里面的nil。
+return: 是一個直接返回給定值,然后立刻結束的函數,比如 f(x) = 213
-bind:是一個非常重要的函數,在Rac Doc中被描述為‘basic primitives, particularly’,它是RACStream監測“值”和控制“運行狀態”的基本方法,個人認為看注釋文檔不能理解它是干嘛的,而且bind英語“捆綁,綁定,強迫,約束”這幾個意思也感覺對不上,我覺得叫“綁架”倒是更貼切一點。在-bind:之后,之前的RACStream就處于被“綁架”的狀態,被綁架的RACStream每產生一個值,都要經過“綁架者”來決定:
1. 是否使這個RACStream結束(被綁架者是否還能繼續活著)
2. 用什么新的RACStream來替換被綁架的RACStream,傳出的結果也成了新RACStream產生的值(綁匪可以選擇再抓一個人質放之前那個前面)
舉個具體栗子,RACStream的 - take:方法,這個方法使一個RACStream只取前N次的值(有縮減):
- (instancetype)take:(NSUInteger)count { Class class = self.class; return [[self bind:^{ // self被綁架 __block NSUInteger taken = 0; return ^ id (id value, BOOL *stop) { // 這個block在被綁架的self每輸出一個值得時候觸發 RACStream *result = class.empty; if (taken < count) result = [class return:value]; // 未達到N次時將原值原原本本的傳遞出去 if (++taken >= count) *stop = YES; // 達到第N次值后干掉了被綁架的self return result; // 將被綁架的self替換為result }; }]];}
-concat: 和 -zipWith: 就是將兩個RACStream連接起來的基本方法了:
除了上面幾個基本方法,RACStream還有不少的Operation方法,這些操作方法的實現大都是組合基本的方法來達到特定的目的,雖然是RACStream這個基類實現的,但我覺得還是放在后面介紹RACSignal的時候作為它的使用方法來說比較合適,畢竟絕大多數編程的對象的都是RACStream的兩個子類,后面再展開介紹好了。
新聞熱點
疑難解答