1. 程式人生 > >ReactiveSwift原始碼解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的程式碼實現

ReactiveSwift原始碼解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的程式碼實現

上篇部落格我們聊完SignalProducer結構體的基本實現後,我們接下來就聊一下SignalProducerProtocol延展中的start和lift系列方法。SignalProducer結構體的方法擴充套件與Signal的擴充套件相同,都是面向協議的擴充套件。首先建立了一個SignalProducerProtocol協議,使SignalProducer在延展中遵循SignalProducerProtocol協議。然後我們再對SignalProducerProtocol進行擴充套件。這樣一來,SignalProducer結構體就擁有了我們在SignalProducerProtocol協議中擴充套件的方法了。這也是我們之前所說的“面向協議的擴充套件”。

今天我們就來聊一下SignalProducerProtocol協議擴充套件中的start和lift系列方法。無論是start系列方法還是lift系列方法,都是在SignalProducerstartWithSignal(setup)核心方法的基礎上構建的。而關於startWithSignal(setup)方法的具體實現,上篇部落格給出了相應的介紹並給出了該核心方法的使用方式,在此就不做過多贅述了。

而在SignalProducerProtocol協議擴充套件中的方法,基本上全是對b方法的封裝,只不過使用場景和功能更為專一,用法更為方便。接下來我們就來看一下SignalProducerProtocol協議擴充套件的start和lift系列方法。

一、Start系列方法

SignalProducerProtocol協議擴充套件中的Start系列方法的主要作用是往SignalProducer中的signal的Bag中新增觀察者的,這一點與Signal的observer()系列方法類似。下方就是start系列的部分方法,在下列的方法中,核心的是start(observer)方法。該方法的引數是一個觀察者的物件,start(observer)方法就負責將該觀察者新增到SignalProducer中的signal的Bag。而下方一系列的start方法都是呼叫的start(observer)方法。

因為Start系列方法極為相似,在此就不一一進行列舉了,下方是部分start方法,其餘省略的與下方程式碼的實現原理一致,在此就不做過多贅述了。

  

看完start系列方法的實現,我們再來看一下start系列方法使用方式。下方是start系列方法的部分使用案例,具體介紹如下:

  • 首先通過SignalProducer的init(value)構造器建立一個producer物件,備用。
  • 然後再建立一個觀察者subscriber1,並給出Value事件的處理閉包。
  • 然後呼叫start(observer)和startWithSignal()方法將subscriber1新增到訊號量中。
  • 之後再呼叫一些列start()方法往signal中新增新的訊號量。

下方的控制檯中是該程式碼段例項的輸出結果,從輸出結果我們我們知道,start()系列方法的主要作用是往signal中新增觀察者的。觀察者新增完畢後,就呼叫SignalProducer建構函式的尾隨閉包。具體程式碼如下所示。

  

二、閉包型別的高階用法

在聊Lift之前呢,我們先來看一下閉包型別使用的示例。因為Lift相關方法的實現較複雜一些,其中涉及閉包方法型別的一些高階用法。接下來我們從Swift語言的角度來看一下函式型別的高階用法,該用法也是在Lift中使用到的,還是有必要單獨的拎出來聊一下的。下方是一些具體的示例。

1、建立MyClass類

首先我們建立一個簡單的類MyClass,該類比較簡單,就是一個屬性、一個構造器、一個add方法。add(other)方法是MyClass的主角稍後我們會用到。因為下方的程式碼比較簡單在此就不做過多贅述了。MyClass下方緊跟著的就是MyClass類測試用例,如下所示。

  

2、建立MyClassProducer類

接下來我們建立一個MyClassProducer類,在該類中使用到了MyClass類。在MyClassProducer中也有一個add方法,只不過該add方法接收的是一個閉包引數,而返回值是一個MyClass型別。add()的引數型別為(MyClass) -> (MyClass) -> MyClass。該閉包型別接收一個MyClass型別的引數,然後返回一個(MyClass) -> MyClass型別的閉包。而(MyClass) -> MyClass型別的閉包的引數又是一個MyClass型別,而返回值是MyClass。

在add(closure)方法中直接執行了closure閉包,並將閉包最終返回的MyClass型別的物件進行返回。具體程式碼如下所示。

  

3、MyClassProducer類的非常規用法

接下來我們就來看一下MyClassProducer類中add(closure)方法直接的使用方式。當然,在正常情況下,上述寫法尤為繁瑣,而是使用方式也是比較麻煩的。下方我們就來看一下直接呼叫add(closure)方法的程式碼。

  • 首先我們建立了一個MyClassProducer型別的物件myProducer1。
  • 然後直接呼叫myProducer1的add(closure)方法。add()的尾隨閉包的引數是MyClass型別的物件myclass1,其返回值是(MyClass)->MyClass型別的閉包,所以我們就直接在尾隨閉包塊中返回了一個閉包塊,該返回的閉包塊的型別就是(MyClass)->MyClass。然後在(MyClass)->MyClass型別的閉包塊中返回了一個MyClass型別的物件,在建立該物件時,使用到了上述兩個閉包的引數myclass1和myclass2。
  • 然後我們將sum01物件的des屬性進行列印,就是closure閉包兩個引數的和。具體結果如下所示。

  

4、MyClassProducer類中add方法的常規用法

上一部分中add(closure)方法的使用方式是非常規用法,因為直接使用add(closure)方法顯得晦澀難懂,而且閉包巢狀閉包,閉包返回閉包的形式著實讓人費解。可以說直接使用沒有什麼好處。接下來我們就來看一下add(closure)的常規使用方式。

下方程式碼片段是add(closure)反覆的常規使用方式。從下方程式碼片段中我們可以直接看出,add(closure)接收的不在是一個閉包,而是MyClass.add(other:)的型別。其實就是把MyClass.add(other:)這個型別所對應的方法體傳給了add(closure)閉包。也就是說add(other:)方法的型別與(MyClass) -> (MyClass) -> MyClass閉包型別是等價的。所以可以將add(other:)方法的方法體提供給add(closure)作為引數。換句話說(MyClass) -> (MyClass) -> MyClass型別等價於MyClass.add(MyClass)->MyClass

這樣做的好處就是可以讓資料與演算法進行分離,add(closure)引數閉包對應什麼樣的演算法那麼add(closure)就執行什麼樣的演算法。這一點在SignalProducer類中的Lift系列方法中表現的淋漓盡致。稍後我們會介紹到。

  

三、Lift系列的核心方法實現

接下來我們就來看一下SignalProducer中的Lift系列方法的程式碼實現。當然,因為Lift系列方法比較多,下方會給出Lift系列方法中比較核心的內容,而剩下的未講解的則是從這些核心方法中延伸出來的方法。接下來我們就由易到難,來看一下Lift系列方法的程式碼實現。

1、lift<U, F>(transform)程式碼實現

該方法算Lift系列中比較獨立而且比較核心的方法了。下方程式碼片段就是該方法的實現。解釋如下:

  • 該方法是一個泛型方法,可以容納兩個泛型<U, F>。其方法引數是一個逃逸閉包transform,而這個閉包的引數是一個型別為Signal<Value, Error>的訊號量,而返回值是型別Signal<U, F>的訊號量,也就是說transform閉包的功能負責將Signal<Value, Error>型別的訊號量經過某些演算法轉換成Signal<U, F>型別的訊號量。而整個函式的返回值是一個SignalProducer<U, F>型別的訊號量生產者。
  • 方法體中,返回了一個新的SignalProducer物件,在SignalProducer構造器的尾隨閉包中呼叫了原SignalProducer物件的startWithSignal()方法。在startWithSignal()方法的尾隨閉包中將原SignalProducer物件的訊號量signal經過transform閉包轉換成一個新的訊號量後,將新SignalProducer物件的observer新增到這個轉換後的訊號量的Bag中,成為其觀察者。具體程式碼如下所示。

  

為了更進一步來了解上述程式碼的實現方式以及執行方式,我們還需結合示例進行分析。下方程式碼片段就是上述方法的使用示例,介紹如下:

  • 首先建立了一個型別為SignalProducer<Int, NoError>的物件producer。在該物件的尾隨閉包中,傳送了一個Value事件,該事件的值為整數8888。
  • 然後通過producer物件的lift方法建立了一個新的物件liftProducer。在lift方法的尾隨閉包中將producer物件內部的Signal<Int, NoError>型別的signal訊號量通過訊號量的map方法將其轉換成Signal<String, NoError>型別的訊號量,並返回。有下方程式碼以及lift()方法的實現容易知道,因為liftProducer物件中的Observer物件被新增到轉換後的Signal<String, NoError>型別的訊號量中作為觀察者,所以liftProducer物件的型別是SignalProducer<String, NoError>
  • 下方程式碼中lift()方法的尾隨閉包就是上述函式實現中的transform的閉包體。下方的signal引數就是transform在呼叫時傳入的引數。
  • 接著,我們有建立了三個型別為Observer<String, NoError>型別的觀察者,然後將這些觀察者都新增進行liftProducer物件的訊號量中。然後我們會看到控制檯上列印的觀察訊息。

  

根據lift(transform)的程式碼實現以及上述示例的執行結果,我們給出了下方的原理圖。下方這個簡圖就是上述示例執行的整個過程,一圖勝千言。根據下圖結合上述示例應該是一目瞭然的。在此就不在過多贅述了。

在SignalProducer的延展中,下方的方法全是在上述lift(transform)的基礎上實現起來的,歸根結底使用的還是Signal中相應的方法。下方這些方法的工作方式以及執行原理和上面這個圖非常相似。只不過是生成中間的訊號量的方式不同。

下方程式碼片段中每個方法在使用lift(transform)方法時使用了尾隨閉包的簡寫形式。其中的$0引數就是尾隨閉包的Signal引數,$0訊號量通過呼叫其對應的方法生成的新的訊號量就是該尾隨閉包的返回值。具體如下所示。當然下方只是部分使用lift(transform)的方法,其他的與下方類似,就不做過多贅述了。

  

2、liftRight<U, F, V, G>(transform)程式碼實現

在看liftRight方法的程式碼實現之前呢,還是需要回顧一下本篇部落格的第二部分閉包型別高階用法的內容的。因為本篇部落格第二部分中的內容以及使用示例有助於理解liftRight方法的使用方式以及執行模式。

下方程式碼片段就是liftRight方法的具體實現,我們需要注意的是liftRight方法是private型別的,也就是說該方法不對使用者直接暴漏,使用者不可以直接呼叫該方法。當然,此刻我們需要看liftRight方法的程式碼實現,要給出相應的使用示例,所以我們可以將private改成public。

從下方程式碼實現中,我們可以直觀的感受到liftRight方法的程式碼實現是比較複雜的。其複雜就複雜在liftRight的入參和返回值都是比較複雜的。首先我們來看一下liftRight引數。其引數是一個名為transform的閉包,該閉包的型別為(Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>,該閉包型別需要一個Signal<Value, Error>型別的引數,其返回值是一個型別的(Signal<U, F>) -> Signal<V, G>閉包。

從該閉包型別,然後在參考第二部分中的(MyClass)->(MyClass)->MyClass閉包型別,以及該閉包型別與MyClass.add(MyClass)->MyClass方法的對應關係。我們不難看出(Signal<Value, Error>) -> (Signal<U, F>) -> Signal<V, G>型別的閉包等價於Signal<Value, Error>.method(Signal<U, F>)->Signal<V, G>型別的方法。而在Signal類中有好多符合Signal<Value, Error>.method(Signal<U, F>)->Signal<V, G>型別的方法,如Signal中的combineLatest、withLatest、take(until:)、skip(until:)等方法,也就是說這些方法的方法體都可以作為transform閉包的閉包體,稍後我們會進行介紹。

  

3、liftRight<U, F, V, G>(transform)直接呼叫

按照老規矩,我們先給出liftRight方法的使用方式,當然此處是liftRight方法的使用方式是非常規的做法,因為我們是直接拿過來用的。不過這樣做更有利於我們理解liftRight的程式碼結構和執行方式。

下方程式碼片段就是我們直接呼叫liftRight方法的示例,介紹如下:

  • 首先我們通過常規方法建立了一個型別為SignalProducer<String, NoError>的物件producer。
  • 然後根據liftRight方法的程式碼實現,建立了兩個類型別名。LiftRightProducerClosureType型別就是producer物件呼叫liftRight方法是所返回的閉包型別,ClosureReturnType則是liftRight方法的引數的閉包所返回的閉包型別。
  • 型別定義好後,就該讓producer物件呼叫liftRight方法了。而下方的liftRightProducerClosure(SignalProducer)則是該方法返回的閉包常量。在該liftRight的返回閉包中,我們將producer物件所對應的訊號量signal以及liftRightProducerClosure閉包所接收的SignalProducer物件中的訊號量otherSignal,呼叫了combineLatest方法進行了合併,具體做法如下。
  • 然後又建立了一個strProducer物件,併為其綁定了一個signal訊號量。然後執行liftRightProducerClosure(strProducer),該閉包會返回一個新的otherProducer物件,緊接著執行otherProducer的startWithValues()方法。然後呼叫strProducer所繫結訊號量的Observer傳送值。具體結果如下所示:

針對上述程式碼的執行過程,還是來張圖來的直接。下方這張簡圖就是上述程式碼的執行過程。執行過程,與上述程式碼的執行步驟是一一對應的。可以根據程式碼的執行步驟後下方的簡圖進行比較。關於下方簡圖,就不做過多贅述了。

4、liftRight<U, F, V, G>(transform)常規使用方式

上面一小節我們直接呼叫了liftRight方法,接下來我們就來看一下liftRight的常規使用方式。所謂的常規使用方式是使用已經實現過的方法的方法體作為transform的閉包體。上面列舉了一些Signal中的方法型別與transform的閉包型別等價的方法其中就有Signal.combineLatest(Signal)->Signal方法。接下來我們就使用combineLatest的方法體來替換上述liftRight方法的尾隨閉包。

下方紅框中就是我們替換的內容,其他程式碼不變。我們發現替換後,輸出結果與我們之前一致。下方的這種使用方式才是liftRight方法正確的使用姿勢

看完上述liftRight的實現以及使用方式,接下來我們就來看一下SignalProducer內部是如何使用liftRight方法的。下方隨便找了一個liftRight的使用方式,舉一反三。

四、liftRight方法與liftLeft方法對比

而在Lift系列方法中,使用liftRight()方法的方式就是上述程式碼段的方式。稍後我們會一一介紹。上述這種技巧使用起來還是比較方便的。出來liftRight方法,還有一個liftLeft()方法。liftLeft()方法的實現方式與liftRight()方法程式碼實現即為相似,只是producer的startWithSignal()方法的呼叫順序不同。接下來我們就來看看這兩者的不同之處。

下方程式碼片段就是liftRight以及liftLeft方法的程式碼實現。經過對比我們不難發現兩者的主要區別是otherProducer和self的startWithSignal()方法的執行順序不同。在liftRight()方法中otherProducer的startWithSignal會先執行完畢,而self的startWithSignal()會後執行完畢。而liftLeft恰好於此相反。

我們以producer.liftRight()(otherProducer)為例,這個Right指右邊的otherProducer的startWithSignal()方法率先執行完畢。而producer.liftLeft()(otherProducer)則指左邊的producer的startWithSignal()方法率先執行完畢。

  

為了更直觀的感受上述兩個方法的不同之處,特此給出了下方的示例。根據下方示例的輸出結果,liftRight與liftLeft的區別一目瞭然。對下方示例的介紹如下:

  • 首先我們建立了兩個SignalProducer的物件,一個傳送0,1,2的值,另一個傳送A、B、C的值。
  • 然後讓producer1物件呼叫liftRight方法, 使用liftRightProducerClosure來暫存返回的閉包,將producer2傳入閉包中。然後呼叫rightProducer的startWithSignal方法。以類似的步驟呼叫lifeLeft方法

  

根據上述程式碼片段的輸出結果,我們不難看出在producer.liftRight()(otherProducer)右邊的otherProducer的startWithSignal()方法率先執行完畢。而producer.liftLeft()(otherProducer)則指左邊的producer的startWithSignal()方法率先執行完畢。

下方是liftLeft()方法是使用方式,與liftRight用法是一致的,如下所示:

在SignalProducer的好多方法中都是在lift、liftRight或者liftLeft方法的基礎上實現的特定功能。以後的部落格會陸陸續續的介紹到。因篇幅有限,今天的部落格就先到這兒,下篇部落格我們會繼續解析ReactiveSwift框架中的其他內容。