logo
hero image

3 Pilar Framework Combine

Sebuah Reactive Framework dari Apple.
1 May 2022 · 8 Minutes

Haloo.. Pada artikel kali ini kita akan berkenalan dengan Combine, mengapa kita membutuhkan Combine, serta bagaimana cara menggunakannya.

Apa itu Combine ?

Combine merupakan sebuah framework yang diperkenalkan oleh Apple pada event WWDC tahun 2019, framework ini mempunyai syntax yang Declarative & ringkas, dan bertujuan untuk membuat program yang Reactive. artinya, jika ada data yang berubah maka data lain yang mempunyai keterkaitan dengan data tersebut pun akan ikut berubah juga.

Untuk memahaminya, perhatikan code berikut :

Imperative Program
icon
1let price = 5000
2var balance = 0
3
4let canBuy = balance >= price
5
6// canBuy -> false
7
8balance = 5000
9
10// canBuy -> tetap false 🤔

Code di atas merupakan salah satu permasalahan jika kita menulis program secara Imperative. Nilai dari variabel canBuy tetap false meskipun kita sudah set variabel balance menjadi 5000.

Nah, Combine hadir untuk mengatasi permasalahan tersebut :

Reactive Program
icon
1let price = CurrentValueSubject<Int, Never>(5000)
2let balance = CurrentValueSubject<Int, Never>(0)
3
4let canBuy = balance
5 .combineLatest(price)
6 .map { $0 >= $1 }
7
8// canBuy -> false
9
10balance.send(5000)
11
12// canBuy -> true 🥳

Untuk sekarang, tidak perlu khawatir jika kamu kurang familiar dengan code di atas, kita akan mempelajarinya sebentar lagi. 😉

Meskipun Combine masih relatif baru dibandingkan Reactive Framework lainnya seperti RxSwift dan hanya support untuk iOS 13 ke atas, tapi menurut saya Combine ini masa depannya lebih cerah, karena dimaintain & mendapat dukungan dari Apple.

Combine juga telah digunakan oleh Apple pada Foundation Framework bahkan SwiftUI. dapat kita lihat pada URLSession.dataTaskPublisher, NotificationCenter, Property Wrapper: @Published, @ObservableObject, @EnvirontmentObject, dan masih banyak lagi.


3 Pilar Framework Combine

Pada dasarnya, cara kerja dari Combine itu cukup sederhana, yaitu Publisher mengirimkan data kepada Subscriber. dan di tengah-tengah pengiriman data tersebut kita dapat memanipulasi datanya menggunakan yang namanya Operator. Koneksi antara Subscriber dengan Publisher dinamakan dengan Subscription. Kalau kita buat diagramnya, kira-kira seperti ini :

Combine DiagramCombine Diagram

1. Publisher

Publisher adalah sebuah Protocol yang menyatakan bahwa ia dapat mengirimkan data dari waktu ke waktu (tidak hanya sekali) kepada Subscriber. Subscriber ini bisa lebih dari satu.

Publisher vs Subject

Berbicara mengenai Publisher, ada satu konsep lagi yaitu Subject. Subject ini mirip seperti Publisher yaitu berfungsi untuk mengirim data kepada Subscriber. bedanya itu kalau Subject bersifat mutable dan kita bisa mengirim datanya secara manual menggunakan method send().

Contoh Publisher :

Ada banyak cara untuk membuat Publisher, salah satunya adalah menggunakan Just, Publisher Just ini berfungsi untuk mengirimkan sebuah data kepada masing-masing Subscriber hanya sekali saja kemudian selesai. Publisher Just juga hanya memegang satu tipe data saja yaitu Output, jadi dia tidak bisa gagal dengan cara mengirimkan Error seperti Publisher CurrentValueSubject pada contoh di atas.

Publisher Just cocok kita gunakan untuk mengirimkan data yang konstan. Berikut adalah contoh penggunaan dari Publisher Just :

Publisher Just
icon
1let publisher = Just(3)
2
3publisher.sink(receiveCompletion: {
4 print("Completed : \($0)")
5}, receiveValue: {
6 print("Value : \($0)")
7})
Output
1Value : 3
2Completed : finished

Selain Publisher Just, Kamu bisa melihat Publisher lainnya yang disediakan oleh Combine di sini : https://developer.apple.com/documentation/combine/publisher pada bagian Relationships -> Conforming Types.

Contoh Subject :

Ada 2 jenis Subject, yaitu PassthroughSubject dan CurrentValueSubject :

  1. PassthroughSubject : Berfungsi untuk merepresentasikan sebuah event.

PassthroughSubject
icon
1let onTappedButton = PassthroughSubject<Void, Never>()
2
3onTappedButton.sink {
4 print("Button Tapped!")
5}
6
7onTappedButton.send()
Output
1Button Tapped!
  1. CurrentValueSubject : Berfungsi untuk merepresentasikan sebuah state.

CurrentValueSubject
icon
1let balance = CurrentValueSubject<Int, Never>(0)
2
3balance.sink { print("Current Balance: \($0)") }
4
5balance.send(5000)
Output
1Current Balance: 0
2Current Balance: 5000

2. Subscriber

Subscriber adalah sebuah Protocol yang menyatakan bahwa ia dapat menerima data yang berasal dari suatu Publisher.

Subscriber memegang 2 tipe data, yaitu Input dan Error. tipe data Input pada Subscriber harus sama dengan tipe data Output pada Pubslisher, begitu pula dengan tipe data Error, keduanya harus memiliki tipe data yang sama.

icon
1Publisher<Output, Error>
2Subscriber<Input, Error>

Membuat Subscription

Koneksi antara Subscriber dengan Publisher dinamakan dengan Subscription. Ada 2 cara untuk membuat Subscription :

1. Menggunakan Operator sink(receiveCompletion:receiveValue:)

Pada beberapa contoh di atas, sebenarnya kita telah menggunakan Operator sink untuk membuat Subscription. Operator sink akan menjalankan closure yang kita berikan saat menerima sinyal Completion dan saat menerima data baru.

Operator sink
icon
1let publisher = Just(3)
2
4publisher.sink(receiveCompletion: {
5 print("Completed : \($0)")
6}, receiveValue: {
7 print("Value : \($0)")
8})
Output
1Value : 3
2Completed : finished

2. Menggunakan Operator assign(to:on:)

Operator assign secara otomatis akan mengubah nilai dari suatu property tiap kali menerima data baru dari Publisher. kita bisa menentukan property tersebut dengan cara memberikan Key Path pada parameter to, dan memberikan object-nya pada parameter on.

Operator assign
icon
1class User {
2 var balance = 0
3}
4
5let user = User()
6let balance = CurrentValueSubject<Int, Never>(0)
7
9balance.assign(to: \.balance, on: user)
10
11balance.send(5000)
12
13print(user.balance)
Output
15000

Menyimpan Subscription

Tiap kali kita membuat sebuah Subscription menggunakan Operator sink, dia akan mengembalikan sebuah instance dari AnyCancellable yang dinamakan dengan Subscription Token. Kita dapat menyimpan Subscription Token tersebut untuk mengontrol kapan sebuah Subscription harus diberhentikan menggunakan method cancel().

Saat method cancel() dipanggil, memory dan resource yang digunakan oleh subscription tersebut akan di-release sehingga bisa digunakan kembali.

Jika kita tidak memanggil method cancel() sebenarnya tidak masalah juga sih, karena instance dari AnyCancellable secara otomatis akan memanggil method cancel() saat deinitialized.

Ada 2 cara untuk menyimpan Subscription Token :

  1. Menyimpan langsung pada variabel.

Store directly to variable
icon
1let publisher = Just(3)
2
4let subscription = publisher.sink(receiveCompletion: {
5 print("Completed : \($0)")
6}, receiveValue: {
7 print("Value : \($0)")
8})
  1. Menggunakan Operator store(in:)

Operator store
icon
2var subscriptions = Set<AnyCancellable>()
3let publisher = Just(3)
4
5publisher.sink(receiveCompletion: {
6 print("Completed : \($0)")
7}, receiveValue: {
8 print("Value : \($0)")
9})
11.store(in: &subscriptions)

Backpressure

Backpressure adalah cara kita untuk mengangani Publisher yang mengirimkan terlalu banyak data kepada Subscriber yaitu dengan cara membatasi data yang masuk yang disebut dengan Demand.

Ada 3 Jenis Demand pada Combine, yaitu :

  • Demand.none

  • Demand.max(value: Int)

  • Demand.unlimited -> default

Untuk menggunakan Backpressure, salah satu caranya adalah dengan membuat sebuah Custom Subscriber. Berikut adalah contoh penggunaan Backpressure :

Backpressure
icon
1class CountSubscriber: Subscriber {
2 typealias Input = Int
3 typealias Failure = Never
4 var subscription: Subscription?
5
6 func receive(subscription: Subscription) {
7 self.subscription = subscription
8 subscription.request(.max(1))
9 }
10
11 func receive(_ input: Int) -> Subscribers.Demand {
12 print("- Received : \(input)")
13 return Subscribers.Demand.none
14 }
15
16 func receive(completion: Subscribers.Completion<Never>) {}
17}
18
19let subject = PassthroughSubject<Int, Never>()
20let subscriber = CountSubscriber()
21subject.subscribe(subscriber)
22subject.send(1)
23subject.send(2)
24subject.send(3)
Output
1- Received : 1

Pada contoh di atas, kita membatasi Demand maksimal 1. Jadi, meskipun kita panggil method send sampai 3 kali atau bahkan 1000 kali, data yang diterima oleh Subscriber hanya yang pertama saja.

Jika kita mau, Kita juga bisa untuk mengubah Demand, namun sifatnya Additive, artinya Demand yang baru akan ditambahkan dengan Demand yang lama.

Backpressure - Adjust Demand
icon
1
2class CountSubscriber: Subscriber {
3 typealias Input = Int
4 typealias Failure = Never
5 var subscription: Subscription?
6
7 func receive(subscription: Subscription) {
8 self.subscription = subscription
9 subscription.request(.max(1))
10
12 DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
13 self.subscription?.request(.max(2))
14 }
16 }
17
18 func receive(_ input: Int) -> Subscribers.Demand {
19 print("- Received : \(input)")
20 return Subscribers.Demand.none
21 }
22
23 func receive(completion: Subscribers.Completion<Never>) {}
24}
25
26
27let subject = PassthroughSubject<Int, Never>()
28let subscriber = CountSubscriber()
29subject.subscribe(subscriber)
30subject.send(1)
31subject.send(2)
32subject.send(3)
33
35DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
36 subject.send(4)
37 subject.send(5)
38 subject.send(6)
39}
Output
1- Received : 1
2// after 1 second :
3- Received : 4
4- Received : 5

3. Operator

Operator adalah method yang dapat kita gunakan untuk memanipulasi data yang akan diterima oleh Subscriber dari Publisher. Perbedaan Operator dengan method biasa adalah Operator akan me-return sebuah Publisher, jadi bisa dibilang bahwa Operator itu adalah "Re-Publisher" sehingga kita dapat gunakan secara chaining seperti ini :

Operator
icon
1[1, 2, 3, 4, 5].publisher
2 .filter { $0 % 2 != 0 }
3 .map { "Odd number : \($0)" }
4 .sink { print($0) }
Output
1Odd number : 1
2Odd number : 3
3Odd number : 5

Masih ingat dengan Program Reactive yang ada pada bagian awal artikel ini? Yup, pada contoh tersebut saya menggunakan Operator CombineLatest, CombineLatest berfungsi untuk mengkombinasikan Suatu Publisher dengan Publisher tambahan yang diberikan. dan akan mem-publish sebuah Tuple yang mana nilainya adalah data terbaru dari kedua Publisher tersebut.

Reactive Program
icon
1let price = CurrentValueSubject<Int, Never>(5000)
2let balance = CurrentValueSubject<Int, Never>(0)
3
4let canBuy = balance
5 .combineLatest(price)
6 .map { $0 >= $1 }
7
8// canBuy -> false
9
10balance.send(5000)
11
12// canBuy -> true

Pada contoh di atas, kita mengkombinasikan Publisher balance dengan Publisher price menggunakan Operator combineLatest, setelah itu kita transform datanya menggunakan Operator map menjadi tipe data Boolean, untuk mengecek apakah saldo-nya cukup untuk membeli suatu produk atau tidak.


Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! 😁 🙏

Referensi :

iOS Development
Swift
Combine

Written by :
Alfin Syahruddin
Developer · Stock Trader · Libertarian · Freethinker

Always open to new ideas. 🕊️

Loading...

Related articles

Articles that you might want to read.

hero image
Integrasi UIKit ke dalam SwiftUI

Tutorial penggunaan UIViewRepresentable dan UIViewControllerRepresentable.

17 April 2022 · 11 Minutes
iOS Development
Swift
SwiftUI
UIKit
hero image
Memahami Symbol pada ES6

Miskonsepsi tentang Tipe Data Symbol

4 November 2019 · 3 Minutes
Javascript
Web Development
hero image
Kustomisasi tema pada Material UI 🎨

Cheatsheet untuk menerapkan Design System pada Material UI

9 April 2021 · 9 Minutes
React
Design System
Material UI
Web Development
All rights reserved © Alfin Syahruddin · 2019
RSS Feed