- vừa được xem lúc

Publisher in Combine: Unveiling the Publisher Role

0 0 10

Người đăng: Phạm Trung Huy

Theo Viblo Asia

Continue with the Combine series, today we will discuss Publisher. Combine needs something that can model a data stream. This is the role of the Publisher protocol.

If you haven’t read my article about Combine in general, let’s check it out: Introduction to Combine in iOS. What is Combine

Now we will focus on types of Publisher regularly used and how to use them

Publishers

Publisher from a Sequence

A data stream can be viewed as a sequence of events over time. Therefore a Sequence is a great source for a simple publisher. Like this example above, Array conforms to Sequence. Sequence has a property called publisher that creates a publisher that emits the element from the source sequence

let arrayPublisher = [1, 2, 3, 4, 5].publisher

Another example:

let stringPublisher = "Huy Pham".publisher

The initial value is a String, after transforming to a publisher, its emitted values are characters The characteristic of this Publisher type is that it never makes errors, so the data type for Failure is Never

Publisher from transformation

We can create a new publisher by using transform operators

[1, 2, 3].publisher // publisher of integers .map(String.init) // now a publisher of strings .sink { print($0) } .store(in: &cancellable)

The map function takes the values emitted by the upstream publisher and passes them as input to the String.init function. So the result Publisher now emits a String rather than an Int

Besides, we can create publishers from other sealed struct of Publishers, be able to transform upstream data to your expected publisher. See the example below:

// combine latest operator
let publisher1 = PassthroughSubject<Int, Never>()
let publisher2 = PassthroughSubject<String, Never>()
let combined = Publishers.CombineLatest(publisher1, publisher2) // create a sequence by sequence operator let numbers = [1, 2, 3, 4, 5]
let sequencePublisher = Publishers.Sequence<[Int], Never>(sequence: numbers)

It’s we simulate the operators. I will have an article next week giving more detail about Combine’s operators.

Publisher from the Class’s property

@Published is a property wrapper that makes any property using it Publisher

This wrapper is class-constrained, meaning that you can only use it in instances of a class

class ViewModel { @Published var title = "the first title"
}

@Published doesn’t need to call send() method or access .value. When you directly change its value, it will update the value. Note that we’re using the dollar sign to access the projected value. If you’re not familiar with this technique, check Apple’s document about Property Wrapper and projected value

var cancellable = viewModel.$title.sink(receiveValue: { newTitle in print("Title changed to \(newTitle)")
})
viewModel.title = "the second title"
// Prints:
// Title changed to the first title
// Title changed to the second title

This approach is very efficient with UIKit and SwiftUI in case you don’t want to change the architecture of your project

Just

Just struct creates a straightforward publisher that will emit one value and then complete

Just("A")

This is handy for use as the return type of a function or if you just want to emit just 1 value

Future

Future means a publisher that eventually produces a single value and then finishes or fails. It provides closure as Future.Promise has Result type parameter without a return value. In the successful case, the future downstream subscriber receives the element before the publishing stream finishes normally. If the result is an error, publishing terminates with that error.

func generateAsyncRandomNumberFromFuture() -> Future <Int, Never> { return Future() { promise in DispatchQueue.main.asyncAfter(deadline: .now() + 2) { let number = Int.random(in: 1...10) promise(Result.success(number)) } }
}
cancellable = generateAsyncRandomNumberFromFuture() .sink { number in print("Got random number \\(number).") } // Prints
// Got random number 9

Future can replace the callback of the function, which can allow you to express asynchronous behavior without deeply nested callbacks (callback hell)

Subject

PassthroughSubject

PassthroughSubject can be used to emit values to downstream subscribers. However, it doesn’t store or cache the most recent value

CurrentValueSubject

Like PassThroughSubject, it can emit values to downstream subscribers. However, unlike PassThroughSubject, CurrentValueSubject maintains and provides access to the most recent value it has received. When a new subscriber attaches to a CurrentValueSubject, it immediately receives the current value (if available) before getting any subsequent updates

import Combine
class DataManager { // Create a PassThroughSubject to emit updates let passThroughSubject = PassthroughSubject<String, Never>() // Create a CurrentValueSubject with an initial value var currentValueSubject = CurrentValueSubject<Int, Never>(0) func updateData(value: String) { passThroughSubject.send(value) } func updateCurrentValue(value: Int) { currentValueSubject.send(value) }
} let dataManager = DataManager() let passThroughSubscription = dataManager.passThroughSubject.sink { value in print("Received value from PassThroughSubject: \\(value)")
}
let currentValueSubscription = dataManager.currentValueSubject.sink { value in print("Received value from CurrentValueSubject: \\(value)")
}
// subjects emit data
dataManager.updateData(value: "Hello, World!")
dataManager.updateCurrentValue(value: 42)
// Prints
// Received value from PassThroughSubject: Hello, World!
// Received value from CurrentValueSubject: 0
// Received value from CurrentValueSubject: 42

The main difference between both subjects is that a PassthroughSubject doesn’t have an initial value or a reference to the most recently published element. Therefore, new subscribers will only receive newly emitted events.

  • A PassthroughSubject is like a doorbell push button When someone rings the bell, you’re only notified when you’re at home
  • A CurrentValueSubject is like a light switch When a light is turned on while you’re away, you’ll still notice it was turned on when you get back home.

Type Erasure

Sometimes you want to subscribe to the publisher without knowing too much about its details or the chain operators create complicated nested type

let publisher = Fail<Int, Error>(error: ErrorDomain.example) .replaceError(with: 0) .map { _ in "Now I am a string" } .filter { $0.contains("Now") }

The type of this publisher is:

Publishers.Filter<Publishers.Map<Publishers.ReplaceError<Fail<Int, Error>>, String>>

To manage these nested types publishers have the eraseToAnyPublisher() method. This is a form of “type erasure”. This method erases the complex nested types and makes the publisher appear as a simpler AnyPublisher<String, Never> to any downstream subscribers.

With AnyPublisher, can’t call send(_:) method, it’s important to wrap a subject with AnyPublisher to prevent the view from sending events through it. When you use type erasure in the MVVM way, you can change the underlying publisher implementation over time without affecting existing clients.

Conclusion

In summary, Combine’s Publishers provides iOS developers with a powerful solution for managing asynchronous events and data streams. Mastering Publishers is essential for creating robust, scalable iOS applications with reactive capabilities.


Thanks for Reading! ✌️

If you have any questions or corrections, please leave a comment below or contact me via my LinkedIn account Pham Trung Huy. Happy coding 🍻

Bình luận

Bài viết tương tự

- vừa được xem lúc

Quản lý self và cancellable trong Combine.

. . Công việc quản lý bộ nhớ memory management thường trở nên phức tạp khi chúng ta thực hiện các tác vụ bất đồng bộ asynchronous vì chúng ta thường phải lưu giữ một số object nằm ngoài scope mà object được define trong khi vẫn phải đảm bảo được việc giải phóng object đó được thực hiện đúng quy trìn

0 0 41

- vừa được xem lúc

Đơn giản hoá mô hình MVVM + SwiftUI

1. Tổng quan.

0 0 21

- vừa được xem lúc

Combine trong Swift: Mở Ra Thế Giới Mới của Lập Trình Reactive

Swift là một ngôn ngữ lập trình mạnh mẽ, được Apple thiết kế để phát triển ứng dụng cho các hệ điều hành của mình. Điều đó có nghĩa là nó không chỉ cung cấp các tính năng cơ bản của một ngôn ngữ lập t

0 0 18

- vừa được xem lúc

RxSwift và Combine, chọn gì trong thế giới lập trình Reactive?

Lập trình Reactive đã trở thành một phong cách lập trình phổ biến trong cộng đồng phát triển phần mềm hiện đại. Trong môi trường phát triển ứng dụng iOS, RxSwift và Combine là hai thư viện quan trọng,

0 0 11

- vừa được xem lúc

Introduction to Combine in iOS

What is Combine. . Key concept. Publisher.

0 0 8

- vừa được xem lúc

Keypaths trong Swift

Thuật ngữ. keypath: Read-only access tới các thuộc tính.

0 0 30