摘要
本文深入剖析 RxSwift,揭露其內部機制,幫助開發者掌握事件處理和效能優化的精髓。 歸納要點:
- RxSwift 的事件處理:深入了解 Subscription 機制、資料傳遞與 Observer 狀態。
- Producer 的運作模式:探討資料流的生成與管理,以及資源釋放機制。
- 效能優化關鍵:分析 Schedulers、資料結構選擇及主題傳遞的效能提升。
深入解析 RxSwift 實作」揭開 observables 和 subscriptions 的運作機制
本教程的主要目的在於深入探討 RxSwift 的核心元件,重點放在其幕後實作上。閱讀本文後,希望您能夠清楚理解什麼是 observables、什麼是 subscriptions,以及 observables 如何運作。本教程假設您已經有一些使用 RxSwift 的經驗,這將使理解更加容易。在 zen8labs,我們經常對成員進行 RxSwift 訓練,因此我們強烈建議您仔細閱讀這篇由 Serg Dort 撰寫的精彩教程:′Learn Rx by implementing Observable′。
特別感謝 Serg Dort 撰寫了這篇非常有幫助的教程。此文章以簡化方式介紹了 RxSwift 的實作,使您易於理解其主要元件及其如何協同工作。
**深入探討 RxSwift 實作**:本文深入探討 RxSwift 的幕後實作,解析 observables 和 subscriptions 的運作機制,提供進階理解。
認識 RxSwift 背後的關鍵概念,掌握資料流處理
再一次,請仔細閱讀教學並逐步遵循程式碼。相信我,這是值得的。坦白說,我認為你不需要知道 RxSwift 的內部運作原理。你只需要專注於何時 Observable 發射元素、何時停止、何時出現錯誤、Observable 可以發射多少個元素,以及何時需要處置你的訂閱。如果你對這些有良好的理解,你可以無問題地使用 RxSwift。但從我的角度來看,我總是想了解我在使用的東西背後發生了什麼。這是確保我做事正確的最佳方式。
讓我們考慮一個簡單的例子:
**專案 1 具體說明**
**典型查詢意圖:**RxSwift 的使用原則
**深入要點:**
* RxSwift 旨在簡化資料流處理,並提供一套強大的工具,讓開發人員可以處理非同步事件、處理序列、與執行緒互動等等。
* 了解 RxSwift 的運作原理有助於開發人員正確使用框架,並避免潛在問題。
* 重點關注 Observable 發射元素、停止、發生錯誤、元素數量和訂閱處置等關鍵概念,即可有效地使用 RxSwift。
**專案 2 具體說明**
**典型查詢意圖:**RxSwift 的進階理解
**深入要點:**
* 除了實務應用,深入了解 RxSwift 的運作原理可以讓開發人員更有效地除錯、最佳化和自訂框架,以滿足特定的需求。
* 透過理解 Observable 、訂閱 、運運算元 和其他組成部分之間的互動,開發人員可以更全面地掌握 RxSwift 的功能。
* 持續學習和研究 RxSwift 的進階概念,可以讓開發人員發揮框架的全部潛力,並開發出更強大、更健全的應用程式。
我們在研究許多文章後,彙整重點如下
- RxSwift 是 Swift 語言中的響應式編程框架,專門用於處理異步事件和數據流。
- 它提供了統一的抽象表述,克服了通知、代理、GCD 和閉包等機制的不足。
- 訂閱生命週期由 DisposeBag 管理,以便在 DisposeBag 被釋放時清除所有資源。
- RxSwift 的資料流呈單向流動,延續了反應式編程的優點,簡化非同步事件處理。
- 在網路層面也能有效使用 RxSwift,例如通過 RxMoya 實現響應式網絡請求。
- 重構項目時,可以採用 MVC, MVC-Rx, MVVM-Rx 和 Coordinators-MVVM-Rx 等模式。
如果你曾經頭疼於管理複雜的異步操作,那麼 RxSwift 絕對是個救星。這個強大的框架不僅讓你的代碼更簡潔,更能幫助你輕鬆處理各種異步事件。不論是資料庫查詢還是網絡請求,都能透過 RxSwift 流暢地進行。而且,它還提供了一致性的抽象層,使得不同功能模塊之間的耦合度大大降低。真的非常適合希望提升開發效率並保持代碼整潔的開發者們!
觀點延伸比較:模式 | 適用場景 | 主要優點 | 主要缺點 |
---|---|---|---|
MVC | 小型應用,簡單架構 | 結構簡單,易於理解和維護 | 不易擴展,當業務邏輯變複雜時難以管理 |
MVC-Rx | 中小型應用,需要部分響應式編程支持的專案 | 引入 RxSwift 增加了響應式數據流處理能力,仍保持 MVC 簡單性 | 可能需要一定的學習成本來掌握 RxSwift 的基本概念 |
MVVM-Rx | 大型應用,複雜界面邏輯和強調數據綁定的專案 | 強大的數據綁定能力,使 View 和 Model 分離更徹底,提高可測試性 | 需要良好的設計規範和較高的學習曲線 |
Coordinators-MVVM-Rx | 非常大型、模組化需求高且需靈活導航控制的大型應用程序 | 清晰的導航流程管理,高度模組化設計提高了代碼復用性與可維護性 | 實現上相對複雜,需要深入理解各個組件間的交互 |
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*U_vheH-__tTdn1pc.png)
RxSwift 運運算元:範例、實現與運作原理
這是一個基本的範例,但它包含了 RxSwift 的大部分重要概念。我們透過組合 `of`、`map`、`filter` 和 `take` 等運運算元建立了一個結果 Observable。然後,我們訂閱結果 Observable 來列印其值。 `subscribe` 方法返回一個訂閱,這是一個 Disposable 型別的例項。就是這樣。在這個範例中有許多隱藏的細節。以下是一些問題:
1. Observable 是一個抽象類,而 Disposable 是一個協議。那麼,這裡實際建立的是什麼型別?
2. 這些運運算元究竟如何工作?
3. 資料是如何經由這些運運算元處理的?
4. 運運算元是如何組合在一起的?
5. 為什麼我們需要透過訂閱來執行邏輯?
6. 訂閱何時結束?
要回答這些問題,我們先來看看這些運運算元的具體實現。
**專案 1:RxSwift 的具體實現**
RxSwift 運用泛型型別來表示可觀察序列(Observables)和可拋棄型別(Disposables),具體如下:
* `Observable<T>`:表示一個產生 `T` 型別值的序列。
* `Disposable`:表示一個可以取消或停止操作的資源,例如網路請求或定時器。
在範例中:
* `result` 是 `Observable<String>` 型別的例項。
* `subscription` 是 `Disposable` 型別的例項,用於管理 `result` 的生命週期。
**專案 2:運運算元的運作原理**
RxSwift 運運算元採用函式式反應式程式設計 (FRP) 原則來操作可觀察序列。以下是範例中運運算元的運作原理:
* `map`: 將輸入序列中的每個元素轉換為新序列中的新元素。
* `filter`: 根據指定的條件從序列中排除不符合條件的元素。
* `take`: 返回序列中指定數量的元素。
這些運運算元透過「管道」(|>)運算子串接在一起,以建立一個新的「運運算元序列」(pipeline)。當資料流經運運算元序列時,每個運運算元都會對資料進行指定的處理。subscribe 方法啟動運運算元序列,並允許外部程式碼監控序列發出的事件(例如值發射或完成)。當序列完成或遇到錯誤時,subscription 就會被取消。
運算子 ′of′:
在 JavaScript 中,`of` 運算子經常與 `for...of` 迴圈一起使用,以便遍歷可迭代的物件,例如陣列、字串、地圖和集合。這個運算子提供了一種簡單而直觀的方法來訪問資料結構中的每一個元素,而不需要像傳統的 `for` 迴圈那樣管理索引。
以下是一個基本示例,展示如何使用 `for...of` 迴圈來遍歷一個陣列:
```javascript
const array = [1, 2, 3, 4, 5];
for (const value of array) {
console.log(value);
}
```
在這段程式碼中,每次迴圈執行時,變數 `value` 都會被賦值為陣列中的下一個元素。這段程式碼將依次輸出:1、2、3、4 和 5。
除了陣列之外,您還可以用相同的方法來遍歷其他可迭代的物件。例如,一個字串也可以透過 `for...of` 迴圈進行遍歷:
```javascript
const string = ′hello′;
for (const char of string) {
console.log(char);
}
```
結果將依次輸出:h、e、l、l 和 o。
對於更複雜的資料結構,例如地圖( Map )和集合( Set )也是如此。請看下面的示例,它們展示瞭如何利用 `for...of` 運算子來便利地訪問每一個元素。
Map 示例:
```javascript
const map = new Map();
map.set(′a′, 1);
map.set(′b′, 2);
for (const [key, value] of map) {
console.log(`¥{key}: ¥{value}`);
}
```
此範例程式將輸出:
```
a: 1
b: 2
```
Set 示例:
```javascript
const set = new Set([1, 2, 3]);
for (const value of set) {
console.log(value);
}
```
此範例程式將輸出:
```
1
2
3
```
`of` 運算子使得處理可迭代物件更加簡潔和高效,是現代 JavaScript 程式設計中不可或缺的一部分。透過了解其應用方式,可以大大提升您的程式碼品質和維護性。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*Qie1ipGwHz65QhY4.png)
對映運運算元(Map operator):
在函數語言程式設計和資料處理的世界中,′map′ 是一個非常重要的概念。它允許您對集合中的每一個元素應用某個函式或轉換,並返回一個新的集合,其中包含應用該函式後的新值。
讓我們來看看一個簡單的例子。在 JavaScript 中,我們可以使用 `Array.prototype.map` 方法來實現這樣的操作:
```javascript
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(x => x * 2);
console.log(doubled); // [2, 4, 6, 8]
```
在這段程式碼中,我們首先定義了一個數字陣列 `numbers`。接著,我們使用 `map` 方法來將每個數字乘以二,並生成了一個新的陣列 `doubled`。
同樣地,在其他語言如 Python 中,也有類似的功能。例如,Python 的內建函式 `map()` 可以達到相同效果:
```python
numbers = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled) # [2, 4, 6, 8]
```
這裡我們使用了 `lambda` 函式來表示匿名函式,同時利用了 Python 的 `map()` 函式進行對映操作。
不僅限於基本的資料轉換,對映運運算元的力量還體現在處理更複雜結構上的效率。例如,在 ′ React ′ 框架中,你可能會看到如下使用方式:
```jsx
const items = [′Apple′, ′Banana′, ′Cherry′];
const listItems = items.map((item) =>
<li key={item.toString()}>
{item}
</li>
);
```
在上述範例中,我們將一組字串轉換成了一系列的 JSX 標籤,以便於渲染到網頁上。
不論是在簡單還是複雜場景下,對映運運算元提供了一種優雅且高效的方法來處理和轉換資料,使得開發者能夠更專注於邏輯本身,而不是繁瑣的迭代過程。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*jyWZSFVxir_YMcA8.png)
過濾運運算元:
在程式設計中,′過濾運運算元′是一個常見且強大的工具,用來處理資料集合。這些運運算元可以讓你根據指定的條件篩選出符合要求的元素。例如,在 JavaScript 中,我們可以使用 ′.filter()′ 方法來對陣列進行過濾。
以下是一個簡單的範例:
```javascript
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // 輸出:[2, 4]
```
在這段程式碼中,`.filter()` 方法會遍歷 `numbers` 陣列中的每一個元素,並將所有能被整除二的數字(即偶數)加入到新的陣列 `evenNumbers` 中。因此,結果是 `[2, 4]`。
過濾運運算元的應用不僅限於基礎資料型別,也可廣泛應用於複雜物件和更大的資料集。例如,我們可以從一個包含多個物件的陣列中篩選出特定屬性符合條件的物件:
```javascript
const people = [
{ name: ′Alice′, age: 25 },
{ name: ′Bob′, age: 30 },
{ name: ′Charlie′, age: 35 }
];
const adults = people.filter(person => person.age >= 30);
console.log(adults); // 輸出:[ {name: ′Bob′, age: 30}, {name: ′Charlie′, age:35} ]
```
在此例中, `.filter()` 方法根據每個人的年齡篩選出了所有年齡大於或等於30歲的人。結果是擁有 `Bob` 和 `Charlie` 的新陣列,他們都超過了該年齡門檻。
′過濾運運算元′提供了一種靈活且高效的方法來處理和分析資料,不論是在小型專案還是大型系統中,都能發揮重要作用。理解和熟練使用這些工具,可以顯著提高你的程式設計能力,使你的程式碼更加簡潔、易讀且功能強大。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*-a2UKai4OTQo5N34.png)
取運算子(Take Operator):
在程式設計中,尤其是在處理資料流時,′取運算子′(Take Operator)是一個強大的工具。它允許我們從一個資料流中提取特定數量的專案,然後終止該資料流的進一步傳送。這對於控制資料流量和資源管理特別有用。
舉例來說,在使用 ′ RxJS ′ 這樣的反應式程式設計庫時,我們可以利用 ′take′ 運算子來優雅地限制要觀察到的事件數量。以下是一些簡單的程式碼示例:
```javascript
import { of } from ′rxjs′;
import { take } from ′rxjs/operators′;
const source¥ = of(1, 2, 3, 4, 5);
const example¥ = source¥.pipe(take(3));
example¥.subscribe(value => console.log(value));
// 輸出:
// 1
// 2
// 3
```
在這段程式碼中,我們建立了一個包含數字序列 `1` 到 `5` 的來源可觀測物件 (`source¥`)。接著,我們利用 `take(3)` 運算子來建立一個新的可觀測物件 (`example¥`),此新物件只會傳送前三個值(一旦達到指定數量,它將自動完成)。訂閱者只會收到並列印前三個數字:`1`, `2`, 和 `3`。
如此一來,我們就能有效地控制從大量來源資料中僅選擇需要的一部分進行處理,而不必擔心因過多未經篩選的資料而導致效能問題或系統資源浪費。
“取運算子”是一種簡單但非常實用的方法,可以幫助開發者更精細地管理和操作他們的資料流,使得程式更加高效和易於維護。在各種不同場景下靈活應用這一工具,你將能夠顯著提升你的程式設計效率與質量。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*59ImAahlTXWRIteh.png)
這裡要強調的是,這四個運運算元都會返回一個 `Producer` 例項。對於 RxSwift 中的其他運運算元也是如此。為了清楚理解 RxSwift 的可觀察物(observables)和運運算元(operators),了解什麼是 `Producer` 是非常重要的。我們來檢視一下 `Producer` 的實作,以深入探討這個概念。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*rIT_K9RNDdk93FUB.png)
RxSwift 魔術方塊:深入剖析 Producer、SinkDisposer 和 run 方法
(1)Producer 類別實際上是 Observable 類別的子類,因此 RxSwift 的運算子基本上就是返回 Producer 不同子類的方法。
(2)每次你訂閱一個 Producer,它會返回一個 SinkDisposer。事實上,Observable 類是一種抽象型別,這意味著我們從不直接建立它的例項。相反,我們使用運算子來建立 observables。因此,我們在日常程式設計中使用的大多數 observables 實際上都是 Producer 的子類。因此,大多數我們在訂閱 observable 時獲得的訂閱其實都是 SinkDisposers。我們範例中的訂閱也是一個 SinkDisposer。
**最新趨勢:** Observable 類別的靜態和共享 экземпляр,可以透過 RxSwift 6 中的新 `asSharedSequence` 擴充方法來建立。這簡化了建立可共用的序列,同時確保其在需要時僅產生一次。
(3)Producer 類別有一個內部抽象方法叫 ′run′。任何 Producer 的子類都需要在這裡實現其業務邏輯。′run′ 方法總是返回兩樣東西:sink 和 subscription。
**深入要點:** 了解 `SinkDisposer` 和 `run` 方法對於使用 RxSwift 建立客製化序列至關重要。`SinkDisposer` 允許訂閱者取消訂閱和釋放資源,而 `run` 方法定義了序列的執行邏輯,包括訂閱和事件產生。掌握這些概念可以提高程式碼的可維護性、效率和效能。
現在,讓我們探索在我們的例子中實現這四個可觀測項(ObservableSequence、Map、Filter、TakeCount),以回答什麼是 sink 和 subscription 的問題。ObservableSequence
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*Fjo2QGhCVuomr2q6.png)
`Map` 方法是 JavaScript 中用於處理陣列的強大工具。它允許我們對每個元素應用一個函式,並返回一個新的陣列,而不改變原始陣列。例如,如果我們有一個包含數字的陣列,我們可以使用 `map` 方法來建立一個新陣列,其中每個數字都增加 1。
```javascript
const numbers = [1, 2, 3, 4];
const incrementedNumbers = numbers.map(number => number + 1);
console.log(incrementedNumbers); // [2, 3, 4, 5]
```
在這段程式碼中,`numbers.map(number => number + 1)` 建立了一個新的陣列 `incrementedNumbers`,其中包含了原始陣列中的每個元素加上 1 的結果。這種方法使得資料轉換變得簡單且直觀。
更進一步地說,`map` 還可以與其他函式結合使用,以實現更複雜的操作。假設我們需要將一組物件中的特定屬性提取出來,可以這樣做:
```javascript
const users = [
{ name: ′Alice′, age: 25 },
{ name: ′Bob′, age: 30 },
{ name: ′Charlie′, age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // [′Alice′, ′Bob′, ′Charlie′]
```
在此例中,透過 `users.map(user => user.name)` ,我們獲得了一個只包含名字的新陣列 `names`。這展示了 `map` 如何能夠輕鬆地從複雜的資料結構中提取出所需的資訊,使程式碼更加易讀和維護。
總之,理解和善用 JavaScript 的 `map` 方法,不僅可以提升開發效率,也能讓你寫出更具可讀性的程式碼。在實際應用中,它是一種不可或缺的工具,有助於處理和轉換資料集合。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*YJ982OGNQCoSAN03.png)
過濾器(Filter)是一種在程式設計中常見的工具,用於從資料集中篩選出符合特定條件的元素。在 JavaScript 中, `filter()` 方法是操作陣列的一個強大功能。這個方法會建立一個新的陣列,其中包含所有透過所提供函式測試的元素。
以下是一個簡單的例子:
```javascript
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // 輸出 [2, 4]
```
在這段程式碼中,我們宣告了一個數字陣列 `numbers` ,並使用 `filter()` 方法來建立一個新陣列 `evenNumbers` 。這個新陣列只包含那些能被2整除的數字,即偶數。匿名函式 `(number => number % 2 === 0)` 是傳遞給 `filter()` 的回呼函式,它對每個元素進行測試,只有符合條件的元素才會被保留到新的陣列中。
應用過濾器不僅限於基本型別,也可以用於複雜資料結構。例如,在處理物件集合時,可以根據物件屬性來篩選:
```javascript
const products = [
{ name: ′Laptop′, price: 1000 },
{ name: ′Phone′, price: 500 },
{ name: ′Tablet′, price: 700 }
];
const expensiveProducts = products.filter(product => product.price > 600);
console.log(expensiveProducts);
// 輸出 [{ name: ′Laptop′, price: 1000 }, { name: ′Tablet′, price: 700 }]
```
上面的範例展示瞭如何使用 `filter()` 方法來挑選價格超過 ¥600 的商品。我們傳遞了一個回呼函式 `(product => product.price > 600)` 給 `filter()` ,它檢查每一項產品是否滿足條件。
透過巧妙運用過濾器,我們可以輕鬆地從海量資料中提取我們需要的資訊,讓程式更加高效且易於維護。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*XbgZnIBDTkKiBqYz.png)
在當今的網頁開發中,′ React ′ 已成為不可或缺的一部分。這個由 Facebook 開發的 JavaScript 庫旨在幫助開發者更加高效地構建使用者介面。透過其元件化和虛擬 DOM 技術,′ React ′ 能夠顯著提升應用程式的效能和可維護性。
一個簡單的例子可以說明 ′ React ′ 的強大功能:假設你有一個需要經常更新內容的動態網站,如新聞門戶或社交媒體平台。在傳統的方法中,每次資料變更都需要重新渲染整個頁面。使用 ′ React ′ 的虛擬 DOM 技術,只需更新實際變更的部分,大幅減少了不必要的渲染操作。因此,不僅提升了網站速度,也改善了使用者體驗。
′ React ′ 也引入了一些前所未見的新概念,如 JSX (JavaScript XML),允許開發者在 JavaScript 中直接撰寫類似 HTML 的標記語法,使得 UI 元件定義更加直觀。例如,下列程式碼展示了一個最基本的 ′ Hello, world! ′ 元件:
```jsx
import React from ′react′;
import ReactDOM from ′react-dom′;
function HelloWorld() {
return <h1>Hello, world!</h1>;
}
ReactDOM.render(<HelloWorld />, document.getElementById(′root′));
```
上述例子中的 `HelloWorld` 元件會被渲染並掛載到 id 為 `root` 的 DOM 元素上。這種基於元件化的方法使得複雜應用程式可以拆分為小型、獨立且可重用的元件,各自負責特定功能,更容易進行開發和測試。
無論是從效能、可維護性還是開發效率角度考量,′ React ′ 都提供了卓越的解決方案。因此,它已經成為現代前端開發者工具箱中的必備利器之一。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*dLkkH17tSBdRZQpX.png)
這裡有什麼相似之處嗎?讓我們專注於這四個 observables 的 run 方法。你可以看到,每一種型別的 observable 都有一個對應的 Sink 類別:ObservableSequence 有 ObservableSequenceSink,Map 有 MapSink,Filter 有 FilterSink,TakeCount 有 TakeCountSink。而這也適用於 RxSwift 中的任何其他 observables。你還可以觀察到,Observables 本身並不執行任何邏輯;它們僅僅是設計或模板,用來描述可觀測邏輯。實際上做所有事情的是 Sink。
為了更容易理解這個概念,我們不妨將 Observable 想像成一台機器的藍圖,而 Sink 例項則是根據這張藍圖建造出來的實際機器。事實上,RxSwift 中所有奇妙的事情都發生在 Sink 裡面。它保持對我們觀察者(observer)的引用,執行 observable 的邏輯,並且將結果事件傳遞給觀察者。如果你想真正了解 RxSwift 中某種型別的 Observable 是如何運作,以及資料是如何被處理的,你只需看看該 Observable 對應的 Sink 就可以了。
RxSwift 效能與組合能力的關鍵原則
另一個需要注意的重要點是,只有在 `run` 方法中訂閱 Observable 時,才會建立 Sink 例項。這解釋了為什麼 Observable 的邏輯僅在訂閱時執行,以及每次訂閱 Observable 時,該邏輯都會重新執行。原因在於,每次訂閱都會產生一個新的 Sink 例項,就這麼簡單。
還有一個元件是 Producer 的 `run` 方法總是返回的——訂閱(subscription)。那麼,什麼是訂閱呢?
在回答這個問題之前,我們先記住 RxSwift 是可組合的,也就是說,你可以將不同型別的 observable 組合成一個,就像由多節車廂組成的一列火車。
**專案 1:最佳化效能的關鍵**
在 RxSwift 中,Sink 例項僅在呼叫 `run` 方法訂閱 Observable 時才建立。此設計有助於最佳化效能,因為 Observable 的邏輯僅在訂閱時才執行,而且每次訂閱都重新執行該邏輯。原因是在每次訂閱時,都會產生新的 Sink 例項。
**專案 2:RxSwift 中的組合能力**
此段落提到 RxSwift 的可組合性,表示可以將不同型別的 Observable 結合成單一 Observable。這種組合功能類似於由多節車廂組成的列車。此功能提供靈活性,使開發人員可以根據需要輕鬆地建立和自定義複雜的資料流。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/1*7cQxRuHzOFzW_ZEsiKODjg.png)
RxSwift 中可觀察序列的生命週期與實作方式
在 RxSwift 中,有兩種型別的可觀察序列(Observables):生成型可觀察序列(Generation Observables)和轉換型可觀察序列(Transformation Observables)。生成型可觀察序列本身會產生事件,例如從 `create`、`of` 和 `from...` 運運算元返回的那些。可以將生成型可觀察序列比作火車頭,所有動力都從它發出,任何事件總是從生成型可觀察序列開始。看看上面的 ObservableSequence 實現,訂閱是執行 sink 例項的結果。這是一種實現 Disposable 協議型別,用於管理為 Sink 執行分配的資源。一旦 Sink 完成生成過程,訂閱將被處置以釋放所有已分配給 Sink 的資源。轉換型可觀察序列則是接收來自其來源的事件並進行轉換。例如,從 `map`、`filter` 和 `take...` 運運算元返回的那些。我們來看一下 Map、Filter 和 TakeCount 的實現。每個類始終有一個來源可觀察序列,它基本上是對鏈中前一個可觀察序列的引用。訂閱是將其 sink 訂閱到其來源可觀察序列後產生的結果。而因為其來源本身也是另一個 Producer,所以該訂閱也就是一個 SinkDisposer。這創造了一個遞迴呼叫,也正是 RxSwift 如何將多個運運算元串聯在一起的方法。
**專案1具體說明:**
RxSwift 中,可觀察序列的生命週期遵循「建立-訂閱-處置」模式。在建立階段,我們使用諸如 `create()`、`of()` 和 `from...` 等運運算元來建構出可觀察序列;在訂閱階段,我們會訂閱這些可觀察序列表達式,同時提供負責處理發射事件的 observers;在處置階段,不論是當完成事件發出或錯誤發生時,我們都要記得適當地處置掉這些已經不再需要使用之資源。
**專案2具體說明:**
我們可以透過串聯不同類別形成流管道,每一個輸出的結果作為下一層級新的輸入,而這整套流程通常依靠像 `map()`、`filter()` 及其他相關轉換運運算元的應用來完成。每次使用某種轉換運運算元,其背後都代表著新的一次事件觸發與更改,使得開發人員能夠設計相對複雜且強大的資料操作管道,在其中每一步驟均執行特定任務,如某些資料篩選或格式變更等。
綜合以上內容,我們可以視覺化我們所舉例之情境如下:
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*8DsEGU5NryR96q_2.png)
在這個過程中,′ React ′ 的虛擬 DOM 技術扮演了關鍵角色。傳統的網頁應用程式通常會直接對真實的 DOM 進行操作,這不僅程式碼複雜,而且效能效率低下。′ React ′ 透過引入虛擬 DOM 改變了這一切。虛擬 DOM 是一個輕量級的 JavaScript 物件,它是對真實 DOM 的抽象表示。
當狀態改變時,′ React ′ 首先會更新虛擬 DOM,而不是立即修改真實 DOM 。然後,它透過高效的“差異演算法”(diffing algorithm)比較新舊兩個虛擬 DOM ,找出需要改變的部分。只將那些改變應用到真實的 DOM 上,從而大幅提升效能。
例如,在一個簡單的計數器應用中,每次按鈕被點選時都會增加計數值並更新顯示。如果沒有使用虛擬 DOM ,每次按鈕被點選後都要重新渲染整個介面。但有了虛擬DOM 之後,只需更新那個顯示計數值的小範圍區域即可,大大減少了瀏覽器重繪和佈局所需時間。
總之,透過使用 ′React′ 和其獨特的虛擬DOM 機制,我們可以建立更加高效、響應迅速且易於維護的大型應用程式。
![](https://miro.medium.com/v2/resize:fit:873/format:webp/0*DFEFELKgMaosMJa3.png)
RxJS訂閱機制探索:世代可觀察物與遞迴訂閱鏈
這是我們範例中的情況:
1. **建立階段**:
結果可觀察物實際上是 TakeCount。TakeCount 的可觀察來源是 Filter,Filter 的可觀察來源是 Map,而 Map 的可觀察來源則是 ObservableSequence。ObservableSequence 是一個世代可觀察物,具有自己發出事件的能力。
2. **訂閱階段**:
當你訂閱結果可觀察物時,你實際上是在訂閱 TakeCount 可觀察物。它會建立一個 TakeCountSink 來執行 TakeCount 的邏輯,並透過將 Filter 可觀察物訂閱到該 Sink 上來建立一個訂閱。Sink 和訂閱都由在你訂閱 TakeCount 時返回的 SinkDisposer 管理。我們範例中的訂閱正是這個 SinkDisposer。
同樣的行為也適用於 Filter、Map 和 ObservableSequence,因為它們也是 Producers。每一步都會建立自己的 Sink 和 SinkDisposer,而且每個步驟中的 SinkDisposer 會持有前一步驟的 SinkDisposer 作為其訂閱。ObservableSequence 是世代可觀察物,因此它的訂閱僅僅是一個管理資源分配以執行 ObservableSequenceSink 本身的例項。
**專案1:世代可觀察物與其監訂機制**
ObservableSequence 作為世代可觀察物,具有主動發出事件的能力,而無需外部輸入。這種特質讓它可以在訂閱階段獨自執行,只需建立資源管理的訂閱即可。
**專案2:遞迴訂閱鏈**
每個 Producer(例如 TakeCount、Filter、Map)在進行訂閱時,都會建立自身的 Sink 和 SinkDisposer。在這些操作中形成了一條遞迴鏈,每一後續步驟中的 SinkDisposer 都持有前一步驟中那一層級別上的 Subscription(即上一層級別上的 SinkDisposer)。此機制確保了所有執行步驟能夠被層次化地管理,也能在取消某一次性操作時正確釋放相關資源。
RxSwift 內部運作:揭露事件處理與資源管理
**3. 執行階段:理解 RxSwift 的管道結構**
ObservableSequenceSink 開始發出事件,而它的觀察者是 MapSink。MapSink 從 ObservableSequenceSink 接收事件,處理其業務邏輯(乘以 2),然後將結果傳送給它的觀察者——FilterSink,依此類推... 最後一個 Sink 是 TakeCountSink,它將事件傳遞給閉包,在我們的例子中列印該值。
**4. 釋放階段:探索 RxSwift 的資源管理**
當我們完成業務時,我們會取消訂閱,實際上這是一個 TakeCount 的 SinkDisposer。然後,此 SinkDisposer 將釋放 TakeCountSink 和其訂閱——即 Filter 的 SinkDisposer。釋放過程如此進行,直到最後一個 ObservableSequence 的 SinkDisposer 被釋放。
這就是 RxSwift 的完整圖景。希望這篇文章能幫助你更深入了解 RxSwift 內部發生了什麼。如果你對其他主題感興趣,可以檢視我們有用的科技見解!
阮全,資深行動工程師
參考來源
Swift Library學習紀錄_RxSwift. 這是一個有點玄的概念實作和應用筆記……
一個Observable,把它當作一條河流,你要的水盆(數據),隨時可能隨著水流漂流下來,設一個觀察機關攔下(訂閱),它來的時候就會漂流到你那裡。
來源: MediumRxSwift學習教程之基礎篇- zzfx
雖然Apple 提供了通知、代理、GCD、閉包等異步機制,但是這些機制缺乏一個統一的抽象表述。另外,這些機制在處理共享的可變數據或狀態時不夠清晰簡練。
來源: 博客园RxSwift 筆記教程
... 訂閱生命週期,就是用 DisposeBag (清除包)來實現訂閱管理機制; 當 DisposeBag 被釋放的時候, DisposeBag 內部的所有可被清除的資源都將被清除. takeUntil : 能夠建立 ...
來源: HackMD傳說中的RxSwift#1 [概念快速理解] - 影山小麥機
所以,誠如上述,RxSwift融合了觀察者模式的訂閱特色,藉由鬆耦合 ... 資料流呈單向流動 4. 延續了Reactive programming的優點,處理非同步事件 ...
來源: Medium如何使用RxSwift(基礎)
RxSwift 是ReactiveX(Reactive Extensions)在Swift 語言中的實現,它是一種用於編寫響應式編程的框架。RxSwift 使您能夠處理異步事件和數據流,並提供了 ...
來源: HackMD移動開發程式設計師應如何打破平庸,杜絕淘汰
其實RxSwift在網路這一層也是有很多響應式處理:. 比如典型的RxMoya. 八 ... 你能做的就是使它儘可能做更多的非同步任務,比如載入遠端或者資料庫資料,解析 ...
來源: tw511教學網RxSwift非同步事件追蹤定位工具的研發歷程
文章概要: 本文主要從分析RxSwift操作符的實現原理入手,然後介紹了Swift反射機制、Swift的函式派發機制及名稱空間機制,同時我們設計了一套實現Hook ...
來源: ITW01「譯」MVVM, Coordinators 和RxSwift 的抽絲剝繭
這個倉庫包含四個文件夾:MVC,MVC-Rx,MVVM-Rx,Coordinators-MVVM-Rx。分別對應重構的每一個步驟。讓我們打開MVC folder 這個項目,然後在進行重構之前 ...
來源: 每日頭條
相關討論