import Combine
import Foundation
class SearchViewModel: ObservableObject {
@Published var searchQuery = ""
@Published var results: [SearchResult] = []
@Published var isLoading = false
@Published var errorMessage: String?
private let searchService: SearchService
private var cancellables = Set<AnyCancellable>()
init(searchService: SearchService = .shared) {
self.searchService = searchService
setupSearchPipeline()
}
private func setupSearchPipeline() {
// Complex pipeline with multiple operators
$searchQuery
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
.removeDuplicates()
.map { $0.trimmingCharacters(in: .whitespaces) }
.filter { $0.count >= 3 }
.handleEvents(receiveOutput: { [weak self] _ in
self?.isLoading = true
self?.errorMessage = nil
})
.flatMap { [weak self] query -> AnyPublisher<[SearchResult], Never> in
guard let self = self else {
return Just([]).eraseToAnyPublisher()
}
return self.searchService.search(query: query)
.retry(2)
.catch { error -> AnyPublisher<[SearchResult], Never> in
self.errorMessage = error.localizedDescription
return Just([]).eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
.receive(on: DispatchQueue.main)
.sink { [weak self] results in
self?.results = results
self?.isLoading = false
}
.store(in: &cancellables)
}
}
class FormViewModel: ObservableObject {
@Published var username = ""
@Published var email = ""
@Published var password = ""
@Published var confirmPassword = ""
@Published var isFormValid = false
@Published var validationErrors: [String: String] = [:]
private var cancellables = Set<AnyCancellable>()
init() {
setupValidation()
}
private func setupValidation() {
// Username validation
let usernameValid = $username
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.map { $0.count >= 3 }
.share()
usernameValid
.map { valid in
valid ? nil : "Username must be at least 3 characters"
}
.sink { [weak self] error in
self?.validationErrors["username"] = error
}
.store(in: &cancellables)
// Email validation
let emailValid = $email
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
.map { email in
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
return NSPredicate(format: "SELF MATCHES %@", emailRegex).evaluate(with: email)
}
.share()
emailValid
.map { valid in
valid ? nil : "Invalid email address"
}
.sink { [weak self] error in
self?.validationErrors["email"] = error
}
.store(in: &cancellables)
// Password validation
let passwordValid = $password
.map { $0.count >= 8 }
.share()
// Passwords match validation
let passwordsMatch = Publishers.CombineLatest($password, $confirmPassword)
.map { $0 == $1 && !$0.isEmpty }
.share()
passwordsMatch
.map { match in
match ? nil : "Passwords do not match"
}
.sink { [weak self] error in
self?.validationErrors["confirmPassword"] = error
}
.store(in: &cancellables)
// Overall form validation
Publishers.CombineLatest4(
usernameValid,
emailValid,
passwordValid,
passwordsMatch
)
.map { $0 && $1 && $2 && $3 }
.assign(to: &$isFormValid)
}
}
// Example of advanced operators
class DataSyncViewModel: ObservableObject {
@Published var localData: [Post] = []
@Published var remoteData: [Post] = []
@Published var syncedData: [Post] = []
private var cancellables = Set<AnyCancellable>()
init() {
// Merge and deduplicate data from multiple sources
Publishers.Merge($localData, $remoteData)
.collect()
.map { allPosts in
var uniquePosts: [Int: Post] = [:]
for post in allPosts.flatMap({ $0 }) {
uniquePosts[post.id] = post
}
return Array(uniquePosts.values).sorted { $0.createdAt > $1.createdAt }
}
.assign(to: &$syncedData)
// Time-based operations
Timer.publish(every: 60, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.refreshData()
}
.store(in: &cancellables)
}
private func refreshData() {
// Refresh logic
}
}
Combine operators transform, filter, and combine publisher streams. I chain operators to build complex async pipelines declaratively. map transforms values, filter removes unwanted elements, removeDuplicates prevents redundant updates. debounce delays emissions for search inputs, throttle limits update frequency. flatMap handles dependent async operations, zip combines multiple publishers. catch provides fallback values on errors, retry retries failed operations. combineLatest merges publishers, emitting when any changes. The sink subscriber receives final values. Understanding operator timing and memory management prevents common bugs. Combine eliminates callback pyramids, making async code readable and testable.
Related snips
import SwiftUI
struct ContentView: View {
@State private var username = ""
@State private var isLoggedIn = false
@StateObject private var viewModel = LoginViewModel()
SwiftUI declarative UI with state management
import SwiftUI
struct CardModifier: ViewModifier {
var backgroundColor: Color = .white
var cornerRadius: CGFloat = 12
var shadowRadius: CGFloat = 5
Custom SwiftUI view modifiers for reusability
import Foundation
import UIKit
class ImageProcessor {
// Background processing with main thread updates
func processImage(_ image: UIImage, completion: @escaping (UIImage?) -> Void) {
Grand Central Dispatch for concurrency
import SwiftUI
enum Route: Hashable {
case postDetail(id: Int)
case userProfile(id: Int)
case settings
Navigation patterns with NavigationStack
import UIKit
import WebKit
class WebViewController: UIViewController {
private var webView: WKWebView!
private var progressView: UIProgressView!
WKWebView for web content display
import SwiftUI
// MARK: - Semantic Colors in SwiftUI
struct AdaptiveColorsView: View {
var body: some View {
VStack(spacing: 20) {
Dark mode support and adaptive colors
Share this code
Here's the card — post it anywhere.