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

ReactiveSwift源碼解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的代碼實現

https 轉換 似的 $0 講解 區別 控制 核心 ont

上篇博客我們聊完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框架中的其他內容。

上述代碼github分享地址:https://github.com/lizelu/TipSwiftForRac

ReactiveSwift源碼解析(九) SignalProducerProtocol延展中的Start、Lift系列方法的代碼實現