If you’re working with Apple’s Swift programming language and Xcode, chances are that you’ll eventually come across Combine. Combine is a powerful framework introduced in iOS 13 and later, which allows you to handle asynchronous and event-driven programming using reactive programming principles. With Combine, you can create complex data flows and handle events easily, making it an essential tool for any Swift developer. In this cheat sheet, we’ll cover some of the most commonly used operators and concepts in Combine.
Contents 非表示
Publishers
In Combine, Publishers are responsible for emitting values or events. They can be thought of as a data source or a producer. Here are some commonly used publishers:
- Just: Creates a publisher that emits a single value and then finishes.
- Future: Creates a publisher that will eventually produce a single value or error.
- Deferred: Creates a publisher that defers the creation of its underlying publisher until a subscriber requests values.
- URLSession.DataTaskPublisher: A publisher that sends a URL request and emits a data object when the request completes.
// Just publisher
let justPublisher = Just("Hello, world!")
// Future publisher
let futurePublisher = Future<String, Error> { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
promise(Result.success("Future Publisher"))
}
}
// Deferred publisher
let deferredPublisher = Deferred {
futurePublisher
}
// URLSession.DataTaskPublisher
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
let urlRequest = URLRequest(url: url)
let urlSessionPublisher = URLSession.shared.dataTaskPublisher(for: urlRequest)
.map(\.data)
.decode(type: Todo.self, decoder: JSONDecoder())
Subscribers
Subscribers are the consumers of Publishers. They receive the emitted values or events and can perform actions with them. Here are some commonly used subscribers:
- Sink: A subscriber that receives a stream of elements and performs an action for each one.
- Assign: A subscriber that assigns each received element to a given property of an object.
- AnySubscriber: A type-erasing subscriber that can receive values of any type.
// Sink subscriber
let sinkSubscriber = urlSessionPublisher.sink { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
} receiveValue: { todo in
print(todo)
}
// Assign subscriber
class ViewModel {
@Published var value: String = ""
}
let viewModel = ViewModel()
let assignSubscriber = justPublisher.assign(to: &$viewModel.value)
// AnySubscriber
let anySubscriber = AnySubscriber(urlSessionPublisher)
Operators
Combine provides a variety of operators to transform and combine publishers. Here are some commonly used operators:
- map: Transforms the elements emitted by a publisher by applying a given closure.
- filter: Only emits elements from a publisher that satisfy a given predicate.
- flatMap: Transforms each element emitted by a publisher into a new publisher and then flattens the resulting publishers into a single stream.
- merge: Combines multiple publishers into a single publisher that emits all their elements.
- zip: Combines the elements of multiple publishers into tuples.
// Map operator
let mapSubscriber = justPublisher
.map { $0.count }
.sink { value in
print("The count of 'Hello, world!' is \(value)")
}
// Filter operator
let filterSubscriber = urlSessionPublisher
.filter { $0.completed }
.sink { todo in
print(todo)
}
// FlatMap operator
let flatMapSubscriber = urlSessionPublisher
.flatMap { todo -> AnyPublisher<User, Error> in
let userId = todo.userId
let url = URL(string: "https://jsonplaceholder.typicode.com/users/\(userId)")!
let urlRequest = URLRequest(url: url)
return URLSession.shared.dataTaskPublisher(for: urlRequest)
.map(\.data)
.decode(type: User.self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
.sink { user in
print(user)
}
// Merge operator
let publisher1 = Just("Hello,")
let publisher2 = Just(" world!")
let mergeSubscriber = Publishers.Merge(publisher1, publisher2)
.sink { value in
print(value)
}
// Zip operator
let zipPublisher1 = Just("Hello,")
let zipPublisher2 = Just(" world!")
let zipSubscriber = zipPublisher1.zip(zipPublisher2)
.sink { value in
print("\(value.0)\(value.1)")
}
Subjects
A subject is a publisher that allows outside sources to send values. It can be thought of as both a Publisher and a Subscriber. Here are some commonly used subjects:
- PassthroughSubject: A subject that allows any number of subscribers and simply passes through (or relays) any values it receives.
- CurrentValueSubject: A subject that has an initial value and emits its current value whenever a new subscriber is added.
// PassthroughSubject
let passthroughSubject = PassthroughSubject<String, Never>()
let passthroughSubscriber1 = passthroughSubject.sink { value in
print("Subscriber 1 received: \(value)")
}
let passthroughSubscriber2 = passthroughSubject.sink { value in
print("Subscriber 2 received: \(value)")
}
passthroughSubject.send("Hello, world!")
// CurrentValueSubject
let currentValueSubject = CurrentValueSubject<Int, Never>(0)
let currentValueSubscriber = currentValueSubject.sink { value in
print("Current value: \(value)")
}
currentValueSubject.send(1)
currentValueSubject.send(2)
Error handling
In Combine, errors are represented by the Error type. You can handle errors using the following operators:
- catch: Catches and handles any error emitted by a publisher by replacing it with another publisher or a default value.
- retry: Resubscribes to a publisher when it fails, up to a given number of attempts.
// Catch operator
let catchSubscriber = urlSessionPublisher
.catch { error -> Just<Todo> in
print("Error occurred: \(error.localizedDescription)")
return Just(Todo(id: 0, userId: 0, title: "", completed: false))
}
.sink { todo in
print(todo)
}
Conclusion
Combine is a powerful framework that allows you to handle asynchronous and event-driven programming using reactive programming principles. This cheat sheet covers some of the most commonly used operators and concepts in Combine. However, there are many more operators and features available, so be sure to check out the official documentation for more information. With Combine, you can create complex data flows and handle events easily, making it an essential tool for any Swift developer.