12/08/2018, 17:55

RxSwift: Các loại Subject [Phần 1]

PublishSubject • PublishSubject: Bắt đầu với giá trị trống và chỉ phát ra các thành phần mới cho subscriber. PublishSubject có ích khi bạn chỉ muốn subscriber được thông báo về các event mới tính từ thời điểm họ đăng ký, cho đến khi họ hủy đăng ký hoặc subject đã chấm dứt với sự kiện .completed ...

PublishSubject

• PublishSubject: Bắt đầu với giá trị trống và chỉ phát ra các thành phần mới cho subscriber. PublishSubject có ích khi bạn chỉ muốn subscriber được thông báo về các event mới tính từ thời điểm họ đăng ký, cho đến khi họ hủy đăng ký hoặc subject đã chấm dứt với sự kiện .completed hoặc .error.

Trong sơ đồ sau đây, dòng trên cùng là PublishSubject và dòng thứ hai và thứ ba là các subscriber. Mũi tên chỉ hướng lên cho biết các mục đăng ký và các mũi tên chỉ xuống hướng đến các event được phát ra.

Subscriber đầu tiên đăng ký sau 1), do đó, nó không nhận được sự kiện đó. Nó nhận được 2) và 3). Và bởi vì subscriber thứ hai không tham gia sau 2), nên nó chỉ nhận được 3). Quay trở lại playground, thêm mã này vào cuối ví dụ:

let subscriptionTwo = subject
  .subscribe { event in
    print("2)", event.element ?? event)
  }

Sự kiện có phần tử optional sẽ chứa phần tử được phát ra cho các event .next. Bạn sử dụng toán tử nil-coalescing ở đây để in phần tử nếu có, hoặc print sự kiện. Như dự kiến, subscribe 2 không print bất kỳ thứ gì vì nó đã đăng ký sau khi 1 và 2 được phát ra. Bây giờ hãy thêm mã này:

subject.onNext("3")

3 được print hai lần, một lần cho subscriber 1 và subscriber 2.

 3
2) 3

Thêm mã này để chấm dứt subscriber 1 và sau đó thêm một sự kiện .next khác vào subject:

subscriptionOne.dispose()
subject.onNext("4")

Giá trị 4 chỉ được in cho subscriber 2), vì subscriber 1 đã được dispose.

2) 4

Khi PublishSubject nhận được sự kiện .completed hoặc .error, còn gọi là sự kiện dừng, nó sẽ phát ra sự kiện dừng đó cho subscriber mới và nó sẽ không còn phát ra sự kiện .next nữa. Tuy nhiên, nó sẽ phát lại sự kiện dừng của nó cho subscriber trong tương lai. Thêm mã này vào ví dụ:

// 1
subject.onCompleted()
// 2
subject.onNext("5")
// 3
subscriptionTwo.dispose()
let disposeBag = DisposeBag()
// 4
subject
  .subscribe {
    print("3)", $0.element ?? $0)
  }
  .disposed(by: disposeBag)
subject.onNext("?")

Dưới đây là những gì bạn vừa làm:

  1. Đặt sự kiện .completed vào subject, sử dụng toán tử on(.completed). Điều này sẽ làm chấm dứt chuỗi quan sát của subject.
  2. Thêm phần tử khác vào subject. Điều này sẽ không được phát ra và được in ra, mặc dù, vì chủ đề đã chấm dứt.
  3. Đừng quên dispose subscribe khi bạn đã hoàn tất!
  4. Tạo một subscriber mới cho subject, lần này thêm nó vào một dispose bag.

Có lẽ người subscriber mới 3) sẽ khởi động subject trở lại? Không, nhưng bạn vẫn nhận được sự kiện .completed.

 2) completed
3) completed

Trên thực tế, mọi loại subject, một khi đã chấm dứt, sẽ phát lại sự kiện dừng của nó cho subscriber trong tương lai. Vì vậy, bạn nên bao gồm các xử lý cho sự kiện dừng trong mã của bạn, không chỉ để được thông báo khi nó chấm dứt, mà còn trong trường hợp nó đã bị chấm dứt khi bạn subscriber nó.

Đôi khi bạn muốn cho subscriber mới biết giá trị phần tử mới nhất là gì, mặc dù yếu tố đó đã được phát ra trước khi đăng ký. Vì điều đó, bạn có một số lựa chọn.

BehaviorSubject

BehaviorSubjects hoạt động tương tự như PublishSubjects, ngoại trừ chúng sẽ phát lại sự kiện .next mới nhất cho subscriber mới. Kiểm tra sơ đồ này: Dòng đầu tiên từ trên cùng là subject. Subscriber đầu tiên trên dòng thứ hai xuống subscribe sau 1) nhưng trước 2), vì vậy nó được 1) ngay khi đăng ký, và sau đó 2) và 3) khi chúng được phát ra bởi subject. Tương tự như vậy, subscribe thứ hai đăng ký sau 2) nhưng trước 3), vì vậy nó nhận được 2) ngay lập tức và sau đó 3) khi nó được phát ra.

Thêm ví dụ mới sau vào playground:

// 1
enum MyError: Error {
  case anError
}
// 2
func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
  print(label, event.element ?? event.error ?? event)
}
// 3
example(of: "BehaviorSubject") {
  // 4
  let subject = BehaviorSubject(value: "Initial value")
  let disposeBag = DisposeBag()
}
  1. Định nghĩa kiểu error để sử dụng trong các ví dụ sắp tới.
  2. Mở rộng về việc sử dụng toán tử ternary trong ví dụ trước, ở đây bạn tạo một hàm trợ giúp sẽ in ra phần tử nếu có, nếu không sẽ in ra lỗi, ngược lại nếu không có lỗi sẽ in ra event.
  3. Bắt đầu một example
  4. Tạo một đối tượng BehaviorSubject mới. Trình khởi tạo của nó có một giá trị ban đầu.

Lưu ý: Vì BehaviorSubject luôn phát ra phần tử mới nhất, bạn không thể tạo phần tử mới nhất mà không cung cấp giá trị ban đầu mặc định. Nếu bạn không thể cung cấp giá trị mặc định ban đầu tại thời điểm tạo, điều đó có thể có nghĩa là bạn cần sử dụng một PublishSubject thay thế.

Bây giờ thêm đoạn mã sau vào ví dụ:

subject
  .subscribe {
    print(label: "1)", event: $0)
  }
  .disposed(by: disposeBag)

Điều này tạo ra một subscribe cho subject, nhưng các subscribe đã được tạo ra sau. Không có yếu tố nào khác được thêm vào subject, vì vậy nó sẽ thay thế giá trị ban đầu cho subscriber.

 --- Example of: BehaviorSubject ---
1) Initial value

Bây giờ hãy chèn mã sau ngay trước mã subscribe trước, nhưng sau khi định nghĩa subject:

subject.onNext("X")

Chữ X được in, vì bây giờ nó là phần tử mới nhất khi subscribe được tạo.

 --- Example of: BehaviorSubject ---
1) X

Thêm mã sau vào cuối ví dụ. Nhưng trước tiên, hãy xem qua và xem liệu bạn có thể xác định nội dung sẽ được in hay không:

// 1
subject.onError(MyError.anError)
// 2
subject
  .subscribe {
      print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
  1. Thêm một sự kiện lỗi vào subject.
  2. Tạo subscriber mới cho chủ đề.

Bạn có nhận ra rằng sự kiện lỗi sẽ được in hai lần, một lần cho mỗi subscribe?

 1) anError
2) anError

BehaviorSubjects hữu ích khi bạn muốn điền trước một lượt xem với dữ liệu gần nhất. Ví dụ: bạn có thể liên kết các điều khiển trong màn hình user profile với BehaviorSubject, để các giá trị mới nhất có thể được sử dụng để hiển thị trước màn hình trong khi ứng dụng tìm nạp dữ liệu mới.

Nhưng nếu bạn muốn hiển thị nhiều hơn giá trị mới nhất thì sao? Ví dụ: trên màn hình tìm kiếm, bạn có thể muốn hiển thị năm cụm từ tìm kiếm gần đây nhất được sử dụng. Đây là lúc sử dụng ReplaySubjects. Các bạn xem trong phần tiếp nhé...

Nguồn: RxSwift Reactive Programming with Swift

0