This chapter introduces a basic class of elements in Combine: Transforming Operators. The conversion operator processes the value from the publisher into a format available to subscribers. You’ll notice similarities between the transform operators in Combine and the common operators in the Swift standard library, such as map and flatMap.
Operators are also Publishers
In Combine, we call the methods that perform operations on values from publishers “operators.” Each Combine operator returns a publisher. Typically, publishers receive upstream events, act on them, and then send the manipulated events downstream to consumers. To simplify this concept, in this chapter you will focus on using operators and processing their output.
Unless the purpose of the operator is to handle an upstream error, it simply republishes the said error downstream
Collect
Publisher that can publish individual values or collections of values. For example, use Publisher when you want to populate a list or grid view
function
The Collect operator provides a convenient way to convert a series of single values from a publisher into an array. To help understand this operator and all the other operators you’ll learn about in this book, you’ll use a pinball graph. The marble diagram helps to visualize how the operator works. The top line is the upstream publisher. The box represents the operator. The bottom line is what the subscriber, or more specifically, what the subscriber will receive after the operator manipulates the value from the upstream publisher. A baseline may also be another operator that receives output from an upstream publisher, performs its operations, and sends those values downstream.
Marble diagrams
Code sample
/ / sink receives the data 5 times [" A ", "B", "C", "D", "E"]. Publisher. Sink {print $0 ()} / / print A B C D E / / sink receives one data [" A ", "B", "C", "D", "E"]. Publisher. Collect (). The sink {print $0) (} / / print (" A ", "B", "C", "D", "E"] / / sink received 3 times (sequence number/collect element parameters, A remainder, add 1) [" A ", "B", "C", "D", "E"]. Publisher. Collect (2) sink {print ($0)} / / print (" A ", "B") (" C ", "D") (" E ")Copy the code
map
In addition to collecting data, you often want to transform it in some way. To do this, Combine provides a variety of mapping operators. The first thing you need to know is Map, which works like Swift’s standard Map, except that it operates on values sent by the publisher.
function
A map operates on a publisher’s Output and converts the data content.
For Output types that are primitives, you can get one of them as map(.x)
Marble diagrams
Code sample
[1, 2, 3, 4, Map {$0 *2}. Sink {print($0)} // print 2 4 6 8 10 // Output = URL(string: "https://www.baidu.com")! Let the pub = URLSession. Shared. DataTaskPublisher (url) that / / the original data, the reponse tuples, converted to a single data. The data map (\. Data)Copy the code
flatMap
FlatMap is one of the most powerful operators in Combine.
The definition is: flatMap operators flatten multiple upstream publishers into one downstream publisher — or, more specifically, flatten the emissions of those publishers.
In fact, flatMap can be simply interpreted as: The function of flatMap is to convert the upstream Publisher to the new Publisher. In general, the Output data type of the new Publisher can be different from the Output data type of the old Publisher, and the error type can also be different.
A common use case for flatMap in Combine is when you want to pass elements emitted by one publisher to a method that itself returns one publisher and ultimately subscribes to elements emitted by the second publisher.
function
FlatMap converts upstream publishers into new publishers.
Marble diagrams
Code sample
[1, 2, 3].publisher.flatmap ({int in // convert original publisher to new publisher // Output of new publisher to Range type return (0.. <int).publisher }).sink(receiveCompletion: { _ in }, receiveValue: { value in print("value: \(value)") })Copy the code
The output is
value: 0
value: 0
value: 1
value: 0
value: 1
value: 2
Copy the code
In practical applications, flatMap can be used in our search process. For example, we type a search keyword in the search bar and click the “Search” button, or click Return on the soft keyboard to search our keyword and Return the search results through network request.
You can see the following code:
ViewModel.swift
class ViewModel: ObservableObject {
/// Search for the keyword Publisher to start the Combine process
var searchPublisher = PassthroughSubject<String.Never> ()private var cancellable = Set<AnyCancellable> ()init(a) {
searchPublisher
.print("_Combine_")
// MARK: - Subscribe to keyword changes through PassthroughSubject
// Convert Publisher to Publisher
,>
// Query the keyword with search
.flatMap { searchContent in
return self.search(searchContent)
}
.receive(on: RunLoop.main)
.sink { [weak self] completion in
switch completion {
case .finished:
print("finished")
case .failure(let error):
print("sink error: \(error)")
}
} receiveValue: { [weak self] searchResult in
print("receiveValue: \(searchResult)")
// We can store the @published variable in the ViewModel to refresh the View
}
.store(in: &cancellable)
}
/// Search by keyword and return to Publisher
/// - Parameters:
/// -text: keyword
/// - Returns: AnyPublisher<StockInfo, Error>
public func search(_ text: String) -> AnyPublisher<StockInfo.Error> {
let url = URL(string: "your request url" + text)
guard let url = url else {
// URL failure returns an error Publisher
return Fail(error: APIError.badURL).eraseToAnyPublisher()
}
// Network request Publisher
return URLSession.shared.dataTaskPublisher(for: url)
.retry(2)
.map { $0.data }
.decode(type: StockInfo.self, decoder: JSONDecoder())
.replaceError(with: [])
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
// Error enumeration definition
enum APIError: Error.CustomStringConvertible {
case badURL
case badNetwork(error: Error)
case badDecode
case unknown
var description: String {
switch self {
case .badURL:
return "_URL Invalided_"
case .badNetwork(let error):
return "_network error: \(error.localizedDescription)_"
case .badDecode:
return "_decode error_"
case .unknown:
return "_unknown error_"}}}}Copy the code
In our View, we can start the search process by declaring the ViewModel variable and then calling searchPublisher.send in the ViewModel.
@StateObject var vm = ViewModel(a).
// called when searchingVm.searchpublisher.send (Search keywords)Copy the code
Without flatMap, we can imagine how to implement this search function.
- Declares a search function for making a web request, passing in the search keyword as an argument
- When the user starts a search, it retrieves the contents of the search box, and then calls the search function, passing in the search keyword as an argument
After using flatMap, the process becomes
- During initialization, create the Combine process
Search keywords change asynchronously — convert to network request — get search results
It’s just one step, and that’s the power of flatMap