ReactiveCocoa這個框架是做什么用的本篇博客就不做過多贅述了,什么是“響應式編程”也不多聊了,自行Google吧。本篇博客的主題是解析ReactiveCocoa框架中的核心模塊ReactiveSwift中的兩個核心類的實現,也就是對Event和Observer這兩個類進行解析。之所以把這兩個類放在一塊聊,是因為這兩個類比較獨立,可以說是ReactiveSwift中的兩個原子類。Event確切的說是一個枚舉,其中有幾種事件,而Observer類的對象就是這些事件的發送者。所以把這兩個類放在一塊是比較合適的。
當然確切的說,本篇博客是對 ReactiveSwift框架 的部分解析,而ReactiveCocoa這個框架又是在ReactiveSwift框架的基礎上搭建起來的,所以我們先來看一下ReactiveSwift這個框架中的代碼實現。當然,我們之前發表過ReactiveCocoa的相關博文,如《iOS開發之ReactiveCocoa下的MVVM》,該篇博客的主題還是ReactiveCocoa框架的應用,而本篇博客或者說ReactiveCocoa源碼解析系列博客是對ReactiveCocoa框架實現的深度解析。當然這種深度解析有一部分是Swift語言層面的,因為ReactiveCocoa框架中有好多Swift語言的高級用法,當然還有一些架構層面的,通過源碼實現,我們要分析出這樣設計的好處以及優點。
拋去“響應式編程”的概念,ReactiveCocoa的本質還是對“觀察者模式”的使用,關於觀察者模式,請參考之前的博客《設計模式(二):自己動手使用“觀察者模式”實現通知機制》。也可以說ReactiveCocoa是“觀察者模式”應用中比較牛X的一個框架。當然,框架在編碼實現時還用到了其他設計模式,在解析到相關內容時,我們在對其進行概述。
當然,本篇博客是對ReactiveSwift源碼的解析,也就是說你可以在你的工程中僅僅的引入 ReactiveSwift框架 ,GitHub地址為:https://github.com/ReactiveCocoa/ReactiveSwift.git,至於如何將ReactiveSwift引入到的你的工程中,請參考ReactiveSwift下方的README, 當然,本篇博客是使用的Cocoapods來實現的版本管理,當然ReactiveSwift也支持Carthage, 如果你是Mac開發的話,還可以使用Swift自帶的包管理器。Swift的包管理器我們在之前聊Swift開發服務端的時候使用到了,不過目前iOS開發中還不能使用Swift自帶的包管理器。相信在不久的將來Swift的包管理器將會支持iOS開發的。閑淡適中,開始我們的主題。
本篇博客我們將先在Swift語言的層面來聊一些東西,因為在Event和Observer實現時會用到。然后我們再解析一下Event和Observe的實現。之前我們聊過Swift語法層面的東西,不過今天還是要在聊一下的,結合着實例還聊語法最為實用。
一、Swift中的泛型
在ReactiveSwift以及ReactiveCocoa中大量的用到了泛型以及關聯類型,所以在聊源碼之前,我們還是有必要回顧一下Swift中的泛型的使用的。當然,只是簡單的回顧一下,不是今天博客的重點。首先我們得通過一個實例來看一下泛型的使用。
下方這個代碼段,就是在協議中使用 associatedtype 關鍵字聲明了一個關聯類型,當然這個關聯類型就相當於協議中的泛型了。下方的這個 GenericityClass 類后邊的<>中聲明的就是該類中使用的泛型類型,我們將該泛型命名為 MyCustomType, 當然我們要求該類型必須是遵循 Comparable 協議的類型,所以聲明該泛型的形式為 <MyCustome: Comparable>。聲明完該泛型后,在類中我們就可以想使用普通類型那樣來使用該泛型了。
泛型不僅僅可以在類中使用,也可以在方法中使用,下方的genericityFunc()方法中就使用了泛型,用法就是在方法名的后方緊跟着泛型,如下所示。
接下來我們來看一下上述泛型類的使用方式。下方代碼首先聲明了一個泛型類的實例,在實例化時,給泛型指定了確定的類型 String。我們還可以為相應的的泛型類型使用 typealias 指定別名,然后使用別名來實例化,如下所示。因為代碼比較簡單,下方測試用例的輸出結果就不往上粘貼了。
二、Swift中的枚舉
因為今天我們要聊的Event就是個枚舉,所以我們先來回顧一下Swift中枚舉的使用。當然還是依托於實例。下方代碼中的枚舉是在我們之前聊Swift的枚舉的主題中拿過來的,並且做了相應的修改。當然在Swift中枚舉以及結構體都是可以使用泛型的,接下來我們就來好好看一下Swift中強大而靈活的枚舉類型。
下方代碼片段中我們定義了一個MobileLanguage枚舉類型,其中有兩個枚舉項。一個是iOS,另一個是Android。枚舉項iOS的枚舉關聯值是一個含有兩個字符串元素的元組,而Android枚舉項的關聯值是一個字符串。下方的iOSValue和androidValue是兩個計算屬性,用來返回相關枚舉項的關聯值。
當然,我們使用 if-case-let語句來獲取相關的枚舉關聯值,具體如下所示。
當然,我們還可以對 “==”運算符進行重載,讓其支持上述定義的枚舉類型的比較。下方主要還是Switch的使用,當然,之前我們也針對過Switch單獨進行過講解,下方就是Switch對元組的匹配,並且在相應的case中獲取枚舉的關聯值,如下所示。
下方就是上述枚舉的使用與輸出結果,如下所示:
三、ReactiveSwift中的Event的實現
接下來我們就來分析一下ReactiveSwift框架中的Event枚舉的代碼實現。我先看其源碼,然后再看其使用方式。
1、Event中的事件類型
下方截圖中就是Event枚舉類型中所包含的所有枚舉項。從下方代碼中我們可以看出,Event后方跟了兩個泛型,一個是Value,另一個是遵循Swift.Error協議的Error泛型。然后緊跟着的是Event枚舉中的幾個事件類型。下方是對這幾種類型的介紹:
-
value: 用來關聯信號量所傳送過來的值,該值的類型就是上面定義的Value泛型。
-
failed: 表示因錯誤而被迫中止的事件,其關聯值是相關的錯誤信息。
-
completed: 該事件是完成事件,也就是所有的東西都success,正常終止。
-
interrupted: 該事件表示被迫中斷的事件,也就是沒有達到預期效果,被迫中止。
2、Event中的 isCompleted 和 isTerminating計算屬性
這兩個屬性是計算屬性,下方是其實現代碼。isCompleted 用來判斷該事件是否是正常完成的事件,而isTerminating主要用來判斷事件是否已經終止,當然其中包括異常終止。當然這兩個計算屬性也是比較簡單的,就是根據不同的條件返回不同Bool值即可。
3、Event中的 value 和 error 計算屬性
下方這兩個也是計算屬性,主要是通過 if-case-let 語句來獲取枚舉的關聯值,並與相應的計算屬性進行關聯。value屬性則用來獲取枚舉項.value所關聯的值。而error則用來獲取枚舉項.failed所關聯的值。具體代碼如下所示。
4、Even計算屬性的測試
接下來,我們就對上述的計算屬性進行測試。下方這段代碼就是對上述計算屬性的測試。首先我們創建了一個類型為 Event<Int, NSError>類型的事件。該事件所關聯的值為100,然后我們輸出計算屬性value、isTerminating、isCompleted計算屬性的值進行打印,具體打印結果如下所示。
然后我們又創建了一個錯誤類型的事件errorEvent。並給該枚舉項關聯一個NSError類型的錯誤對象。然后對error、isTerminating、isCompleted的值進行打印。從打印結果可以看出isTerminating為true,說明是終止事件,而isCompleted為false,則說明是非正常終止。
5、Event中的map函數
在Event枚舉中,主要有兩個map函數,一個是map<U>()泛型函數。另一個是mapError<F>()泛型函數。因為mapError<F>()函數的實現與map<F>()函數的實現極為相似,我們此處就以mapError<U>()泛型函數為例。也就是下方這個完整的函數。
map<U>()函數是一個泛型函數,在函數名map后緊跟的<U>就是我們定義的泛型。而該函數的參數是一個閉包 f, 該閉包的類型為 (Value) -> U。也就是說該閉包的有一個Value類型的參數,並且返回一個U類型的返回值。map<U>()這個函數的返回值是一個新的事件,該事件的類型為Event<U, Error>。經過這么一分析,map<U>()函數就是將當前的 Event<Value, Error> 類型的事件映射成Event<U, Error>類型的事件。當然此處的Value和U都是泛型,當然如果換成具體的參數的話,也就是說一個 Event<Int, Error> 類型的參數可以通過下方的方法來映射成 Error<String, Error> 類型的事件。
下方我們需要主要的是返回值 .value( f(value) ) 這句話,.value()的關聯值是f(value)這個閉包所返回的值,而f(value)這個閉包的參數是之前事件所綁定的值。而f(value)所返回的值就是要映射的結果類型。f()的閉包體由用戶來提供,也就是說用戶可以自定義映射規則。
6、map函數的測試用例
接下來我們來看一下Map函數的使用方式。下方代碼段就是Map函數的測試用例以及運行結果。首先我們創建了一個類型為 Event<Int, NSError> 類型的事件,然后該事件的value值為100。 然后我們調用map函數將 Event<Int, NSError> 類型映射成 Event<String, NSError>類型。然后map函數后邊跟隨的尾隨閉包就是我們的映射規則。你可以在該閉包中添加任意的映射規則,將原來的值轉換成你想要的值。
mapError<F>()函數的實現以及使用方式,與上述函數類似。接下來我們就來看一下mapError<F>()函數的使用方式。首先我們定義了兩個錯誤類,一個是MyError另一個是MyError1。並且定義了一個Event<Int, MyError> 類型的錯誤事件,然后調用 mapError<F>()函數將其轉換成 Event<Int, MyError1> 類型的事件,當然調用時提供的閉包仍然是映射規則。具體如下所示。
Event枚舉中還有對 == 號運算符的重載,使Event類型的參數支持 == 運算符。其中還有一個將事件類型轉換成description描述字符串的 extension。因為其內容比較簡單,在此就不做過多贅述了。
四、ReactiveSwift中的Observer
聊完Event的實現,我們來看一下Observer類的實現。Observer的主要職能是對Event進行使用,也就是Observer可以調用自己的方法來發送Event中所提供的各種事件的。下方就是對Observer類的詳細解析。
1、Observer類中屬性以及構造器的解析
接下來我們來看一下Observer類中所聲明的屬性以及構造器。首先我們注意到,Observer類也是也一個泛型類,在Observer類名后方分別跟着 Value 和Error: Swift.Error兩個泛型。這兩個泛型分別與Event后邊的泛型相對應,Value就是事件所關聯值的類型,而Error就是發生錯誤時錯誤的類型。
緊接着是聲明了一個 (Event<Value, Error>) -> Void 的閉包類型,並且為該類型聲明了一個Action的別名。然后使用這個Action的別名聲明了一個action的不可變屬性。而Observer的構造器的參數就是一個類型為(Event<Value, Error>) -> Void 的閉包。
Observer還聲明了一個便利構造器。該便利構造器有四個可選類型的參數,每個參數的類型都是一個閉包。這四個可選類型的閉包參數分別與Event中的四種事件相對應,在便利構造器中調用Observer的構造器時,提供了Action閉包的閉包體,在Action閉包體中,根據具體的事件類型來執行便利構造器參數所提供的相應閉包參數。當然便利構造器的閉包參數由Observer的使用者所提供,用來回調相應事件中的值。
根據上面的源代碼我們不難看出,在初始化Observer的對象時,我們可以調用構造器,也可以調用便利構造器來進行初始化。當然,還是推薦使用便利構造器來實例化Observer類的實例。下方第一個就是使用的便利構造器來實例化Observer的,並且在調用是提供了四個閉包回調,來分別處理Observer發來的不同事件。
當然你也可以直接調用 Observer所提供的構造器,也就是直接為Action閉包賦值。第二段代碼中的尾隨閉包就是Action的閉包體,當然我們需要自己處理Action針對不同事件是所給出的處理塊。
2、Observer中發送事件的方法sendXXX()
接下來我們就來看一下Observer中發送各種事件的方法,當然Event有四種事件類型,那么Observer中也就是是4個發送事件的方法了。下方代碼片段就是Observer中發送事件的方法,從下方的方法中我們不難看出,發送事件其實就是對 action閉包的調用,並且傳入相應的事件。在調用 action 閉包時,就會執行我們所提供的或者遍歷構造器中所提供的閉包體,將發送的事件回調出去。
3、sendXXX()方法的測試用例
上面我們已經通過Observer的構造器和便利構造器實例化兩個實例,接下來我們就調用這些實例所對應的send方法。下方代碼片段就是對相應Observer實例的相關send方法的執行。
下方就是上述測試用例的執行結果
五、Observer工作的流程圖
看完上述代碼,因為閉包回調會導致一些代碼的執行流程已經調用關系不太容易理解,解析來我們就來畫一個圖來簡述Observer的具體工作過程。下方代碼就是上述測試用以的執行以及調用的過程。
下方是一個完整的程序執行過程,輸入->處理->輸出。
因篇幅有限,今天的博客就先到這兒,下篇博客我們會繼續解析ReactiveSwift框架中的其他內容。
上述代碼github分享地址:https://github.com/lizelu/TipSwiftForRac