Giới thiệu về Combine trong Swift

iOS Sep 16, 2021
  1. Giới thiệu

Combine đã được Apple giới thiệu như một framework mới tại WWDC 2019. Framework này cung cấp một declarative API Swift  để xử lý các giá trị theo thời gian và có thể được coi là một giải pháp thay thế “chính chủ” cho các framework phổ biến như RxSwift và ReactiveSwift.

Apple đã tự tạo ra Combine để giải quyết, tối ưu bài toán về reactive programing. Combine được sinh ra để giúp các lập trình viên IOS có thể dễ dàng tiếp cận , đồng thời xử lý tốt hơn trong các bài toán yêu cầu kĩ thuật lập trình bất đồng bộ.

Bất kỳ điều gì xảy ra theo thời gian trong ứng dụng của bạn đều có thể được xử lý bằng Publisher: Networking, Sự kiện người dùng, Notifications, KVO, v.v.

Trong Combine có một số khái niệm cốt lõi cần được hiểu và trong bài viết này mình cũng xin xoay quay các khái niệm chính này.

  • Publisher và Subscriber
  • Operator
  • Subjects

2. Publisher

2.1. Khái niệm

Hãy bắt đầu khái niệm đầu tiên, đây là đối tượng có thể quan sát phát ra giá trị bất cứ khi nào một sự kiện nhất định xảy ra. Các Publisher có thể hoạt động vô thời hạn hoặc kết thúc khi hoàn thành và cũng có thể phát tín hiệu khi gặp lỗi.

Publisher là một protocol  định nghĩa các các yêu câu cho type để có thể truyền một chuỗi các value theo thời gian đến một hoặc nhiều Subcribers. Hay 1 Publisher sẽ publish hoặc phát ra sự kiện kèm theo value.

Tất cả các Publisher đều phải kế thừa protocol này. Bạn sẽ thấy có 2 kiểu yêu cầu bạn phải cung cấp

  • Output : chính là kiểu giá trị cho dữ liệu bạn phát ra
  • Failure: kiểu dữ liệu cho trường hợp lỗi

Do đó, 1 Publisher sẽ phát ra 1 trong 3 kiểu giá trị sau:

  • value : chính là cái chúng ta cần, dữ liệu
  • error : nếu gặp lỗi thì các subscriber sẽ nhận được như vậy
  • complete : là kết thúc vòng đời đau khổ của 1 publisher

2.2. Một số loại Publisher

Just

Đây là 1 Publisher đặc biệt. Nó sẽ phát ra 1 giá trị duy nhất tới subscriber và sau đó là finished. Khi khởi tạo 1 Just thì bạn cần phải cung cấp giá trị ban đầu cho nó. Kiểu giá trị của Output sẽ dựa vào kiểu giá trị bạn cung cấp.

Giá trị của Just vẫn có thể là:

  • value
  • error
  • finished

Future

Future là một publisher đặc biệt, nó phát ra chỉ một value và sau đó là complete. Một Future có thể được sử dụng để tạo ra một asynchronously cung cấp chỉ một value và sau đó complete. Nó sẽ thực hiện một lời hứa Promise. Đó là 1 closure với kiểu Result, nên sẽ có 1 trong 2 trường hợp:

  • Success : phát ra Output
  • Failure : phát ra Error
  • Subject

Cũng là một loại Publisher đặc biệt, bạn có thể inject giá trị vào stream thông qua hàm send(:) .

Có hai loại Subject chính là PassthroughtSubjectCurrentValueSubject. Điểm khác nhau của 2 loại là với CurrentValueSubject khi khởi tạo sẽ được truyền vào một giá trị khởi tạo và sẽ được emit khi có subscriber nó

Xem ví dụ bên dưới để thấy sự khá biệt nhé:

Ngoài ra để còn một số cách tạo ra Publisher khác mình sẽ giới thiệu trong phần “Combine và Foundation”, các bạn tham khảo ở cuối bài nhé.

3. Subscriber

Đây là thực thể ở cuối cùng trong cả quá trình tương tác. Nó sẽ nhận các giá trị từ Publisher. Và cũng như publisher, thì tất cả các subscriber để phải kế thừa protocol Subscriber.

Ngược lại với Publisher, thì Subscriber sẽ cần dữ liệu đầu vào và ta phải cung cấp kiểu dữ liệu cho Input. Tất nhiên, bắt buộc phải cung cấp kiểu dữ liệu cho Failure.

Tiếp theo là 3 function quan trong nhất:

  • receive(subscription:) khi nhận được subscription từ Publisher
  • receive(input:) khi nhận được giá trị từ Publisher và chúng ta sẽ điểu chỉnh request tiếp dữ liệu thông qua Demand . Có nghĩa bạn muốn nhận tiếp hay không hay nhận hết, thì bạn có thể tuỳ ý quyết định … đây là ưu điểm mà Combine hơn người tiền nhiệm RxSwift.
  • receive(completion:) khi nhận completion từ publisher.

Nhìn vào cấu trúc của Subscriber ta thấy có một đặc điểm  cần chú ý đó là  “Subscriber nhận được giá trị từ Publisher và chúng ta sẽ điểu chỉnh request tiếp dữ liệu thông qua Demand ”, điều này rất quan trọng, nó giúp cho lập trình viên có thể kiểm soát được số lượng value truyền từ Publisher tới Subscriber. Chúng ta có một bài toán như sau:

Giả sử chúng ta có một Publisher sẽ xử lý và phát ra một số lượng giá trị rất lớn, vậy làm sao để cho Subscriber có thể nhận được những giá trị đó một cách toàn vẹn nhất. Combine cung cấp cho Subscriber có khả năng request các tùy chọn nhận dữ liệu tới Publisher, sau đó Publisher sẽ đáp ứng và phát ra đúng theo request của Subscriber. Có vẻ hơi khó hiểu đúng không? Cũng xem code demo nhé.

Tiếp theo tạo 1 Publisher như sau:

Tiến hành chạy thử, kết quả không nhận được bất cứ giá trị nào cả.Tiếp theo sửa code thành như sau:

Ta chỉ nhận được 5 giá trị sau đó không nhận thêm được gì nữa,Tiếp tục sửa lại như sau:

Kết quả đã nhận được toàn bộ giá trị mà Publisher phát ra đúng không?

Giải thích như sau: Mỗi lần nhận được dữ liệu, thì Subscriber lại điều chỉnh request của mình thông qua Demand, Với việc return về:

  • ​​.none : không lấy thêm phần tử nào nữa
  • .unlimited : lấy tất cả phần tử
  • .max(n) : lấy tối đa n phần tử tiếp theo

Cứ như vậy, theo ví dụ trên. Ban đầu subscription request lấy 1 giá trị, sau đó Subscriber điều chỉnh lấy thêm 1 giá trị nữa. Nó sẽ lặp đi lặp lại cho tới hết.

Như vậy, bạn có 2 nơi có thể điều chỉnh việc request dữ liệu từ Subscriber tới Publisher. Và bạn chủ động trong việc xử lý dữ liệu nhận được từ Publisher.Đây chính là ưu điểm của Combine so với các framework khác đó.

3.1 Sink

Sink cách đơn giản nhất mà bạn có thể dùng để tạo ra 1 Subscriber, bạn cũng có thể thấy mình dùng nó trong các ví dụ bên trên để demo đúng không.

Với Sink bạn không cần quan tâm gì nhiều tới các đối tượng hay class

Bạn cung cấp cho nó 1 closure để xử lý các giá trị nhận được hay completion từ Publisher

3.2 Assign

Đây là cách phổ biến thứ 2 khi bạn muốn có 1 subscriber. Lần này thì đặc biệt hơn một chút. Bạn có thể gán giá trị của mình tới 1 property của 1 object nào đó.

Trong ví dụ trên những  giá trị được publisher phát ra được gán vào thuộc tính .text của textfiled, khi nhận được giá trị thì textfield sẽ hiển thị nội dung tương ứng. Ví dụ khác tương  tự chúng ta Assign vào 1 class như sau:

Tạo 1 đối tượng Subscriber với Assign. Nhằm đưa dữ liệu nhận được trực tiếp tới name của đối tượng staft.

Cuối cùng ta tạo publisher và phát dữ liệu đi.

Rất tiện đúng không nào? Tuy nhiên nhược điểm Assign là chúng ta phải đảm bảo publisher không bao giờ phát đi error.

3.3 Canclelable

Khi bạn đăng ký subscriber cho publisher thì subscription trả về sẽ là 1 cancellable. Nó có tác dụng sẽ tự hủy trong trường hợp publisher đã hoàn thành việc phát hết giá trị, Chính xác hơn là là nó sẽ bị hủy đi khi mà ViewController của bạn biến mất chẳng hạn. Bạn cũng có thể chủ động hủy nó bằng cách gọi phương thức cancle() có sẵn của nó.

4. Life Cycle

Để hiểu hơn về vòng đời giữa publisher và subscriber các bạn có thể tham khảo sơ đồ bên dưới

Bước 1: Subscriber đăng ký (subscribe) tới Publisher

Bước 2: Khi nhận được đăng ký từ Subscriber thì Publisher gởi về cho Subscriber một subscription

Bước 3: Subscriber sẽ request để Publisher phát đi giá trị.

Bước 4: Publisher sẽ gửi giá trị đi, số lượng giá trị gửi đi sẽ được subscriber quyết đinh thông qua việc trả về cho Publisher một lời hứa (Demand)

Bước 5: Publisher sẽ gởi completion/error để kết thúc chuỗi đăng ký này

5. Combine và Foundation

Như đã nói ở phần giới thiệu, Apple đã tích hợp rất sâu Combine vào trong những gì sẵn có của Swift. Hầu như mọi tác vụ đều có thể thực hiện thông qua publisher. Trong phần tiếp theo dưới đây mình xin giới thiệu một ví dụ tích hợp Combine vào code một cách dễ dàng nhất.

Biến property thành publisher

Giả sử 1 struct hay 1 class của chúng ta có sẵn 1 property là isLoading, để chuyển nó thành publisher ta chỉ cần thêm  @Published như một Anotation.

Tạo Publisher từ giá trị

Chúng ta có thể tạo publisher từ các kiểu dữ liệu như String, Array, Dictionary

Đặc trưng của loại Publisher này là không bao giờ có lỗi. Hay kiểu dữ liệu cho Failure là Never.

Notification với Combine Với Notification Center chúng ta cũng có thể dễ dàng sử dụng publisher thông qua phương thức .publisher như ví dụ bên dưới.

Networking với CombineTiếp  theo mình xin ví dụ về sử dụng Combine khi dùng URLSesssionTask

Trên đây là một số ví dụ việc kết hợp Combine thông qua các đoạn code rất quen thuộc, Các bạn thấy chúng rất quen thuộc và dễ tiếp cận đúng không? Apple đã tích hợp rất sâu vào các components có sẵn để giúp người dùng dễ dàng chuyển đổi code sang Combine hơn. Tới đây mình thấy quan điểm cho rằng với Combine thì hầu hết mọi thứ đều có thể thực hiện thông qua publisher có vẻ rất chính xác.

6. Operator

Operator là các method đặc biệt được gọi trên các publisher và trả về cùng hoặc một publisher khác. Một operator mô tả một hành vi để thay đổi value, thêm value, loại bỏ value hoặc nhiều hoạt động khác. Bạn có thể xâu chuỗi nhiều toán tử lại với nhau để thực hiện xử lý phức tạp.

Hãy nghĩ về các giá trị nhận được từ publisher gốc thông qua một loạt các toán tử. Giống như một dòng sông, các giá trị đến từ publisher này và đi đến publisher khác.

Có 3 loại Operator chính : Transforming Operator, Filter Operator, Combining Operator.

Chúng ta cùng tìm hiểu khái niệm và một vài operator quan trọng thuộc mỗi loại nhé.

6.1 Transforming Operator Transforming Operators là nhóm các toán từ làm biết đổi giá trị dữ liệu của publisher hoặc biến đổi cả publisher thành 1 publisher khác.

Collecting Value: là một operator mạnh mẽ cho phép chúng ta nhận tất cả các kết quả đầu ra cùng một lúc hoặc theo số lượng từ một publisher

Nó thu thập  các giá trị đã nhận và phát ra một mảng duy nhất hoặc theo số lượng collections cho tới khi publisher  kết thúc.

Ví dụ: Mỗi lần subscriber sẽ nhận được 3 value cho tới khi hết tất cả giá trị

Mapping Values:  Đây là những toán tử chuyển đổi kiểu giá trị này thành kiểu giá trị khác. Dựa theo việc ánh xạ từ phần tử này sang phần tử kia. Với một quy luật nào đó mà mình đặt ra.

Các loại Mapping Values bao gồm:

  • map: Dùng để biến đối kiểu giá trị này thành kiểu giá trị khác
  • map key path: dùng để biến đổi các thuộc tính của 1 đối tượng
  • tryMap: dùng để biến đổi như map, nhưng sử dụng với các kiểu dữ liệu có nguy cơ sinh ra lỗi. Khi có lỗi thì tự động chúng sẽ vào completion với error
  • flatMap: là toán tử biến đổi 1 publisher này thành 1 publisher khác

6.2 Filter Operator

Filter Operator là nhóm các operator có tác dụng lọc các giá trị từ các publisher phát ra theo một điều kiện nào đóCác operator bao gồm:

  • removeDuplicates bỏ các các phần tử liên tiếp giống nhau. Chỉ giữ lại 1 mà thôi.
  • compacMap tương tự như map. Nhưng nó sẽ auto bỏ đi các phần tử mà không biến đổi được
  • ignoreOutput khi bạn không muốn nhận gì từ publisher ngoại trừ completion
  • first(where:) & last(where:) tìm kiến phần tử đầu tiên hoặc cuối cùng thoả điều kiện
  • drop bỏ đi các phần tử theo 1 quy luật nào đó
  • prefix  giữ lại các phần tử theo 1 quy luật nào đó

6.3 Combining Operator

  • prepend : add thêm các giá trị vào trước các giá trị mà publisher phát
  • append : add thêm các giá trị vào sau các giá trị mà publisher phát
  • switchToLastest : Chỉ nhận value cuối cùng
  • merge(with:) : kết hợp các publisher với nhau
  • combineLastest : kết hợp các giá trị với nhau từ các publisher khác nhau theo cặp, và lấy giá trị mới nhất của từng publisher

Tổng kết

Trên đây là một số kiến thức cơ bản của mình tìm hiểu và chia sẻ về framework Combine của Apple, hi vọng với những chia sẻ này sẽ giúp cho các bạn sẽ có cái nhìn tổng quan và dễ dàng tiếp cận với framework vô cùng mạnh mẽ này để ứng dụng tốt hớn trong code của mình. Cám ơn các bạn đã đọc hết bài viết của mình!

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.