07/09/2018, 17:03

RxSwift: Bài 4 - Subjects (Part 2) - BehaviorSubject và ReplaySubject

RxSwift: Bài 4 - Subjects (Part 2) - BehaviorSubject và ReplaySubject 1. BehaviorSubject BehaviorSubject tương tự như PublishSubject ngoại trừ chúng sẽ nhận giá trị gần nhất của .onNext event đến những new subscribers . Xem sơ đồ sau: Dòng thứ nhất là BehaviorSubject, 2 dòng tiếp theo ...

RxSwift: Bài 4 - Subjects (Part 2) - BehaviorSubject và ReplaySubject

1. BehaviorSubject
BehaviorSubject tương tự như PublishSubject ngoại trừ chúng sẽ nhận giá trị gần nhất của .onNext event đến những new subscribers. Xem sơ đồ sau:
alt text

Dòng thứ nhất là BehaviorSubject, 2 dòng tiếp theo là 2 subscribers của nó.
Sub01 đăng kí trước 1) và sau 2). Nó nhận được 2), 3) là đương nhiên, ngoài ra nó còn nhận đc 1) vì đó là giá trị gần nhất được phát trước khi ta subscribe
Sub02 đăng kí trước 2) và sau 3). Nó nhận được 3) là rõ ràng, ngoài ra nó nhận được thêm 2) vì đây là giá trị gần nhất được phát trước khi nó subscribe

Được rồi, đến đây ta bắt tay vào đoạn code như sau:
Thêm cho mình 1 enum là myError kế thừa Error bên trong có 1 case là anError. Sau đó, thêm cho mình những đoạn code sau:
alt text

Mình giải thích một chút:
1.Mình tạo 1 func print để cho tiện hơn thôi, ở đây mình giải thích đoạn toán tử ?? một chút, mình sẽ hiểu như sau:

  • event.element ?? (event.error ?? event) -> nếu element không nil thì trả về event.element, nếu nil thì trả về cái thằng bên trong dấu ngoặc này.
  • bên trong dấu ngoặc, nếu error không nil (tức là nó bị error) thì trả về event.error không thì trả về event. Event này thì bạn đã biết event gì rồi đấy, là completed.

2.Tiếp theo mình tạo 1 BehaviorSubject với giá trị ban đầu. Lưu ý, vì BehaviorSubject luôn luôn phát ra phần tử gần nhất nên bạn không thể tạo ra nó mà không cung cấp giá trị ban đầu. Nếu bạn không thể cung cấp giá trị ban đầu tại thời điểm khởi tạo, có nghĩa là bạn cần dùng PublishSubject thay vì BehaviorSubject
Tiếp theo là subscribe nó:
alt text

Đoạn trên tạo 1 subscription đến cái subject, và không có bất kì elements nào được add vào subject, cho nên nó sẽ phát lại giá trị ban đầu đến thằng subscriber. Kết quả thu được sẽ là:
alt text

Bây giờ bạn thêm giá trị cho subject trước khi nó đc ai đó subscribe nhưng sau thời điểm khởi tạo:
alt text

Và kết quả sẽ là tín hiệu gần nhất:
alt text

Ta code thêm chút nữa cho hiểu thêm:
alt text

Các bạn đoán được kết quả chứ, đúng rồi, sub02 sẽ nhận được error.
alt text

Tóm lại:
BehaviorSubject được dùng khi bạn muốn thông báo cho 1 ai đó về dữ liệu gần nhất. Ví dụ như login 1 app, đôi khi bạn muốn show lại latest value để hiển thị trong form login chẳng hạn.

Còn nếu trong search screen, bạn muốn show 5 giá trị gần nhất thì dùng cái gì bây giờ, vì BehaviorSubject chỉ hiển thị 1 cái. Đó chính là ReplaySubject.

2. ReplaySubject
ReplaySubject sẽ tạm thời lưu trữ lại các cache hay bộ đệm (buffer) của nhiều value gần nhất, tối đa số lượng sẽ phụ thuộc vào mình chọn. Nó sẽ phát lại bộ đệm này cho những subscribers mới. Hãy xem sơ đồ dưới đây:
alt text

Cái sub01 thứ nhất sẽ subscribe ngay từ đầu nên nó sẽ nhận toàn bộ event mà subject phát ra. Cái sub02 subscribe sau 2) và trước 3) và size of buffer của nó = 2 nên nó sẽ nhận 2 giá trị gần nhất trước đó nên nó sẽ nhận luôn toàn bộ giá trị.

Hãy lưu ý khi sử dụng ReplaySubject thì bộ đệm của nó sẽ được lưu trong bộ nhớ. Bạn sẽ dễ gặp phiền phức như là bộ đệm của bạn quá lớn hay loại lưu trữ của bạn là hình ảnh dung lượng lớn. Một điều cần lưu ý là tạo 1 ReplaySubject của array, buffer size sẽ lưu nhiều array, rất dễ bị issue về bộ nhớ nếu không cẩn thận.

Xem ví dụ sau:
alt text

Phân tích các dòng code như sau

  1. Khởi tạo ReplaySubject trong đó sử dụng create(bufferSize:) để khai báo size của bộ đệm.
  2. Add thêm 3 giá trị vào subjects
  3. Tạo 2 subscriptions của subjects

Hai phần tử gần nhất là 2, 3 sẽ được phát đến cả 2 subscribers, 1 sẽ không được phát vì giới hạn bộ đệm là 2 - ta đã setup cho nó trước khi có bất kì ai subscribe (nếu ta tăng lên 3 thì được). Kết quả thu được như sau:
alt text

Bây giờ ta thêm 1 value cho cái subject này và thêm 1 subscription Sub03 cho nó,
alt text

Các bạn đoán thử kết quả? Đúng rồi. Sub01, Sub02 sẽ tự động nhận giá trị 4, vì nó đang đăng kí, trong khi Sub03 sẽ nhận giá trị 3, 4 chứ không phải 2, 3 như Sub01, Sub02 đã nhận trước đó.

Sub01: 4
Sub02: 4
Sub03: 3
Sub03: 4

Đến đoạn này, mình hi vọng các bạn đã hiểu rõ về hai loại subject này. Bây giờ đặt một vấn đề là nhỡ may bị error thì làm sao? Hãy add thêm error vào vị trí như sau:
alt text

Các bạn có thể đoán được kết quả không ? Lần này mình sẽ add kết quả trước rồi giải thích sau, các bạn hãy nhìn vào kết quả rồi tự rút ra kết luận cho mình trước khi đọc phần giải thích nhé:

Sub01: 4
Sub02: 4
Sub01: anError
Sub02: anError
Sub03: 3
Sub03: 4
Sub03: anError

Có thể giải thích như sau? Cái replaySubject này đã kết thúc với 1 error, đương nhiên Sub01, Sub02 sẽ biết chuyện này và nhận event Error này. Và cái quan trọng ở đây là nó vẫn sẽ vẫn phát 2 giá trị trước đó trước khi phát Error event cho những subscribers mới.

Bây giờ, add dòng code này ngay sau khi error,
alt text

thì kết quả như sau:
alt text

Bằng cách gọi dispose() của ReplaySubject trước đó, thì các new subscribers sẽ chỉ nhận được error event mà nói rõ rằng subject đã được xử lý xong rồi (was already disposed)

Gọi dispose() lên replay subject kiểu như này không phải cách mình thường hay làm, bởi vì nếu bạn đã thêm subscriptions của bạn vào trong disposeBag (chủ yếu để tránh tạo ra những strong reference cycles) thì mọi thứ sẽ bị xử lý và huỷ đi khi chủ của nó (trong thực tế thường là ViewController hoặc ViewModel). Dù sao, đây cũng là một kiến thức khá hiếm và hay, nên biết.

Như vậy, với các loại subject hiện tại, bạn có thể xử lý hết nhu cầu cần thiết. Bây giờ lỡ bạn đơn giản là chỉ muốn hỏi observable type là giá trị hiện tại là gì? thì lúc đó Variable sẽ rất có ích.

0