00. Combine (0) ———— A Simple Example

It will take about 3 minutes to finish reading this article.

Next, I will record the learning process of Combine through a series of articles.

1. ReactiveCocoa、RxSwift and Combine

1.1 Comparison

ReactiveCocoa (RAC), RxSwift, and Combine are all frameworks for handling reactive programming (Reactive Programming). They are designed to help developers handle asynchronous event streams, data binding, and event processing more easily.

ReactiveCocoa first introduced the concepts of Signal and Subscriber, and it also provides a set of functions for processing event streams and data binding. RxSwift is the Swift version of ReactiveX (Rx) and also uses the Publisher and Subscriber models.

Combine was introduced by Apple in iOS 13 and macOS 10.15 as part of Swift specifically for the Swift programming language. Combine also uses the Publisher and Subscriber model, which is based on Swift language features and has been integrated into Apple’s native frameworks, such as Foundation and UIKit, making it more collaborative with other parts of the Apple ecosystem.

1.2 Common points

Combine, RxSwift, and ReactiveCocoa all adopt the Publisher and Subscriber model, where a stream of events (such as user input, network responses, etc.) is represented as an observable sequence, and these events can then be processed by subscribers.

They all support data binding, which can automatically bind the data model and UI elements together. When the data changes, the UI will automatically update.

They both provide a set of operators for processing and transforming event streams, making it easier to write complex asynchronous operations.

They all emphasize composability, allowing multiple operations and event streams to be combined into more complex operations and data processing processes.

They are all designed to simplify asynchronous programming and handle asynchronous tasks such as network requests, timers, and multi-threaded operations.

2. A Simple Example

Here is an example of SwiftUI and Combine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import SwiftUI
import Combine

struct Item {
let title: String
}

extension Notification.Name {
static let titleData = Notification.Name("Title_Data")
}


class ItemViewModel: ObservableObject {
@Published var titleText: String = "Hello Swift!"

private var cancelables: Set<AnyCancellable> = []
private var cancel: AnyCancellable?

init() {
// register
let publisher = NotificationCenter.Publisher(center: .default, name: .titleData)
.compactMap { notification -> String? in
return (notification.object as? Item)?.title
}

// Binding
publisher
.assign(to: \.titleText, on: self)
.store(in: &cancellables)
}

func requestData() {
let data = Item(title: "Hello Combine!")
NotificationCenter.default.post(name: .titleData, object: data)
}
}

struct ContentView: View {
@ObservedObject var viewModel = ItemViewModel()

var body: some View {
VStack {
Button("Please Click") {
viewModel.requestData()
}

Text(viewModel.titleText)
.padding()
.padding()
}
}
}

Execution result:

1
Hello Swift!

Click button, becomes:

1
Hello Combine!

This involves using the Combine framework to handle the publication and subscription process of notifications. Here’s an explanation of these two lines of code:

(1) NotificationCenter.Publisher(center: .default, name: .titleData):

This line of code creates a Combine publisher that subscribes to notifications associated with a specific notification name. Here, .default means using the default notification center, and .titleData is a custom notification name (Notification.Name) used to identify the notification type you are interested in. This notification name is usually defined elsewhere to ensure that both publishers and subscribers use the same notification name.

(2) .compactMap { notification -> String? in ... }:

This part is the transformation of the publisher just created. .compactMap is a Combine operator, which is used to transform or filter the data extracted from the notification.

In this closure, notification is the notification object, and we try to extract data from the object property of the notification, where object is expected to be an Item object. If the advised object is of type Item and has a title attribute, then the closure returns an optional string (String?), which is the title of the Item.

Note that type conversion is done using the as? keyword to ensure that the notification’s object property is of type Item. If the conversion is successful, returns the title; otherwise, returns nil.

(3) publisher.assign(to: \.titleText, on: self):

This line of code binds the publisher created above to the property that receives the data. The .assign operator assigns the publisher’s output value (a string in this case) to a specific property.

\titleText is a keyPath that specifies the attribute that receives the data. In this example, assume that self has a property called titleText which will be used to store the title string extracted from the notification.

(4) .store(in: &cancellables):

This line of code stores the subscription relationship in the cancellables collection to ensure that the subscription can be properly canceled when it is no longer needed. cancellables is usually a Set type variable used to hold the Cancellation identifier of the Combine in order to unsubscribe when the object is released to avoid memory leaks.

This code creates a Combine publisher that receives a notification of a specific name from the NotificationCenter and extracts the data from the notification as a string and assigns the extracted string to the object’s titleText property. The subscription relationship is then stored in cancellables to ensure that the subscription can be canceled when needed. This way, the value of the titleText property will be automatically updated when a specific notification is received.