|
| 1 | +// |
| 2 | +// CustomPublisher.swift |
| 3 | +// SwiftUI-CommandLineTool |
| 4 | +// |
| 5 | +// Created by Janesh Suthar on 01/08/25. |
| 6 | +// |
| 7 | + |
| 8 | +import Foundation |
| 9 | +@preconcurrency import Combine |
| 10 | + |
| 11 | +//Build a custom Combine operator |
| 12 | +//Let’s build an operator called .isEmptyOrWhitespace() for Publisher<String, Failure> |
| 13 | +//It emits: |
| 14 | +//true if the string is empty or whitespace |
| 15 | +//false otherwise |
| 16 | + |
| 17 | +// 1. Define a custom Publisher wrapper |
| 18 | +struct IsEmptyOrWhitespacePublisher<Upstream: Publisher>: Publisher where Upstream.Output == String { |
| 19 | + typealias Output = Bool |
| 20 | + typealias Failure = Upstream.Failure |
| 21 | + |
| 22 | + let upstream: Upstream |
| 23 | + |
| 24 | + func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input { |
| 25 | + upstream |
| 26 | + .map { str in |
| 27 | + str.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty |
| 28 | + } |
| 29 | + .receive(subscriber: subscriber) |
| 30 | + } |
| 31 | +} |
| 32 | +// 2. Add a convenience extension on Publisher |
| 33 | +extension Publisher where Output == String { |
| 34 | + func isEmptyOrWhitespace() -> IsEmptyOrWhitespacePublisher<Self> { |
| 35 | + return IsEmptyOrWhitespacePublisher(upstream: self) |
| 36 | + } |
| 37 | +} |
| 38 | +// 3. Usage |
| 39 | +func printIsEmptyOrWhitespace() { |
| 40 | + _ = Just(" ") |
| 41 | + .isEmptyOrWhitespace() |
| 42 | + .sink { isEmpty in |
| 43 | + print("Is empty or whitespace:", isEmpty) // true |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +// MARK: - A custom Combine operator that retries a failing publisher N times, with a delay between attempts. Super useful for: Network requests, Database retries, Resilient logic in unstable environments |
| 48 | + |
| 49 | +extension Publisher { |
| 50 | + func retryWithDelay(retries: Int, delay: TimeInterval, scheduler: DispatchQueue = .main) -> AnyPublisher<Output, Failure> { |
| 51 | + self.catch { error -> AnyPublisher<Output, Failure> in |
| 52 | + guard retries > 0 else { |
| 53 | + return Fail(error: error).eraseToAnyPublisher() |
| 54 | + } |
| 55 | + |
| 56 | + return Just(()) |
| 57 | + .delay(for: .seconds(delay), scheduler: scheduler) |
| 58 | + .flatMap { _ in |
| 59 | + self.retryWithDelay(retries: retries - 1, delay: delay, scheduler: scheduler) |
| 60 | + } |
| 61 | + .eraseToAnyPublisher() |
| 62 | + } |
| 63 | + .eraseToAnyPublisher() |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | + |
| 68 | +func testFailingPublisher() async throws { |
| 69 | + let failingPublisher = Future<Int, Error> { promise in |
| 70 | + print("Attempting request...") |
| 71 | + promise(.failure(NSError(domain: "Network", code: -1))) |
| 72 | + } |
| 73 | + _ = failingPublisher |
| 74 | + .retryWithDelay(retries: 3, delay: 2) |
| 75 | + .sink(receiveCompletion: { completion in |
| 76 | + print("Completion:", completion) |
| 77 | + }, receiveValue: { value in |
| 78 | + print("Got value:", value) |
| 79 | + }) |
| 80 | +} |
0 commit comments