Combine Framework: āļāļēāļĢāđ€āļ‚āļĩāļĒāļ™āđ‚āļ›āļĢāđāļāļĢāļĄāđ€āļŠāļīāļ‡āļĢāļĩāđāļ­āļāļ—āļĩāļŸāđƒāļ™ Swift

āđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļ Combine āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļŠāļ•āļĢāļĩāļĄāļ‚āđ‰āļ­āļĄāļđāļĨāđāļšāļš asynchronous āđƒāļ™ Swift: Publishers, Subscribers, Operators āđāļĨāļ°āļĢāļđāļ›āđāļšāļšāļ‚āļąāđ‰āļ™āļŠāļđāļ‡āļŠāļģāļŦāļĢāļąāļšāđāļ­āļ› iOS

āļ„āļđāđˆāļĄāļ·āļ­ Combine framework āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāđ€āļ‚āļĩāļĒāļ™āđ‚āļ›āļĢāđāļāļĢāļĄāđ€āļŠāļīāļ‡āļĢāļĩāđāļ­āļāļ—āļĩāļŸāđƒāļ™ Swift iOS

āļāļēāļĢāđ€āļ‚āļĩāļĒāļ™āđ‚āļ›āļĢāđāļāļĢāļĄāđ€āļŠāļīāļ‡āļĢāļĩāđāļ­āļāļ—āļĩāļŸāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ§āļīāļ˜āļĩāļāļēāļĢāļˆāļąāļ”āļāļēāļĢāđ€āļŦāļ•āļļāļāļēāļĢāļ“āđŒāđāļšāļš asynchronous āđāļĨāļ°āļŠāļ•āļĢāļĩāļĄāļ‚āđ‰āļ­āļĄāļđāļĨāđƒāļ™āđāļ­āļ› iOS āļ­āļĒāđˆāļēāļ‡āļŠāļīāđ‰āļ™āđ€āļŠāļīāļ‡ Combine āļ‹āļķāđˆāļ‡āđ€āļ›āđ‡āļ™āđ€āļŸāļĢāļĄāđ€āļ§āļīāļĢāđŒāļāļ”āļąāđ‰āļ‡āđ€āļ”āļīāļĄāļ‚āļ­āļ‡ Apple āļ™āļģāđ€āļŠāļ™āļ­āđāļ™āļ§āļ—āļēāļ‡āđāļšāļš declarative āđāļĨāļ°āļ›āļĨāļ­āļ”āļ āļąāļĒāļ—āļēāļ‡āļŠāļ™āļīāļ”āļ‚āđ‰āļ­āļĄāļđāļĨ āđ€āļžāļ·āđˆāļ­āļˆāļąāļ”āļ§āļēāļ‡āđ„āļ›āļ›āđŒāđ„āļĨāļ™āđŒāļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™ āļ„āļđāđˆāļĄāļ·āļ­āļ™āļĩāđ‰āļžāļēāļœāļđāđ‰āļ­āđˆāļēāļ™āļˆāļēāļāđāļ™āļ§āļ„āļīāļ”āļžāļ·āđ‰āļ™āļāļēāļ™āđ„āļ›āļˆāļ™āļ–āļķāļ‡āļĢāļđāļ›āđāļšāļšāļ—āļĩāđˆāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™āļˆāļĢāļīāļ‡

āļ—āļģāđ„āļĄāļ–āļķāļ‡āđƒāļŠāđ‰ Combine āđāļ—āļ™ RxSwift?

Combine āļ•āļīāļ”āļ•āļąāđ‰āļ‡āļĄāļēāļžāļĢāđ‰āļ­āļĄāļāļąāļš iOS 13+ āđƒāļŦāđ‰āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ”āļĩāļāļ§āđˆāļēāļ”āđ‰āļ§āļĒāļāļēāļĢāļ›āļĢāļąāļšāđāļ•āđˆāļ‡āļ‚āļ­āļ‡ Apple āđāļĨāļ°āđ€āļŠāļ·āđˆāļ­āļĄāļ•āđˆāļ­āļāļąāļš SwiftUI āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ„āļĢāđ‰āļĢāļ­āļĒāļ•āđˆāļ­ āđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļ”āļđāđāļĨ dependency āļ āļēāļĒāļ™āļ­āļ

āđāļ™āļ§āļ„āļīāļ”āļŦāļĨāļąāļāļ‚āļ­āļ‡ Combine

Combine āļŠāļĢāđ‰āļēāļ‡āļ‚āļķāđ‰āļ™āļšāļ™āđāļ™āļ§āļ„āļīāļ”āļŠāļģāļ„āļąāļāļŠāļēāļĄāļ­āļĒāđˆāļēāļ‡: Publishers āļ—āļĩāđˆāļŠāđˆāļ‡āļ„āđˆāļē, Subscribers āļ—āļĩāđˆāļĢāļąāļšāļ„āđˆāļē āđāļĨāļ° Operators āļ—āļĩāđˆāđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ—āļąāđ‰āļ‡āļŠāļ­āļ‡ āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāļ™āļĩāđ‰āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡āđ„āļ›āļ›āđŒāđ„āļĨāļ™āđŒāļ‚āđ‰āļ­āļĄāļđāļĨāđāļšāļšāļĢāļĩāđāļ­āļāļ—āļĩāļŸāļ—āļĩāđˆāļ›āļĢāļ°āļāļ­āļšāđ€āļ‚āđ‰āļēāļ”āđ‰āļ§āļĒāļāļąāļ™āđ„āļ”āđ‰

Publisher: āđāļŦāļĨāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ

Publisher āļ„āļ·āļ­āļŠāļ™āļīāļ”āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļŠāļēāļĄāļēāļĢāļ–āļŠāđˆāļ‡āļĨāļģāļ”āļąāļšāļ‚āļ­āļ‡āļ„āđˆāļēāļ­āļ­āļāļĄāļēāļ•āļēāļĄāđ€āļ§āļĨāļēāđ„āļ”āđ‰ Publisher āđāļ•āđˆāļĨāļ°āļ•āļąāļ§āļˆāļ°āļ›āļĢāļ°āļāļēāļĻāļŠāļ™āļīāļ”āļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āļŠāļ­āļ‡āļŠāļ™āļīāļ”: āļŠāļ™āļīāļ”āļ‚āļ­āļ‡āļ„āđˆāļēāļ—āļĩāđˆāļŠāđˆāļ‡āļ­āļ­āļ (Output) āđāļĨāļ°āļŠāļ™āļīāļ”āļ‚āļ­āļ‡āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ—āļĩāđˆāļ­āļēāļˆāđ€āļāļīāļ”āļ‚āļķāđ‰āļ™ (Failure) āļ•āđˆāļ­āđ„āļ›āļ™āļĩāđ‰āļ„āļ·āļ­āļ§āļīāļ˜āļĩāļŠāļĢāđ‰āļēāļ‡ Publisher āđāļ•āđˆāļĨāļ°āļ›āļĢāļ°āđ€āļ āļ—:

PublisherBasics.swiftswift
import Combine

// Just: emits a single value then completes
// Useful for converting a simple value to a Publisher
let singleValue = Just("Hello Combine")

// CurrentValueSubject: stores and emits the current value
// Perfect for representing state that changes over time
let counter = CurrentValueSubject<Int, Never>(0)

// PassthroughSubject: emits values without storing them
// Ideal for one-time events (taps, notifications)
let buttonTaps = PassthroughSubject<Void, Never>()

// Future: emits a single value asynchronously
// Wraps an async operation that returns a result
let asyncOperation = Future<String, Error> { promise in
    // Simulate a network call
    DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
        promise(.success("Data loaded"))
    }
}

āļŠāļ™āļīāļ” Never āļŠāļģāļŦāļĢāļąāļš error āļŦāļĄāļēāļĒāļ„āļ§āļēāļĄāļ§āđˆāļē Publisher āļˆāļ°āđ„āļĄāđˆāļĄāļĩāļ§āļąāļ™āļĨāđ‰āļĄāđ€āļŦāļĨāļ§ āļ‹āļķāđˆāļ‡āđ€āļ›āđ‡āļ™āļāļēāļĢāļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™ āļ“ āļ•āļ­āļ™āļ„āļ­āļĄāđ„āļžāļĨāđŒāļ—āļĩāđˆāļŠāđˆāļ§āļĒāļ—āļģāđƒāļŦāđ‰āđ‚āļ„āđ‰āļ”āļˆāļąāļ”āļāļēāļĢ error āđ€āļĢāļĩāļĒāļšāļ‡āđˆāļēāļĒāļ‚āļķāđ‰āļ™

Subscriber: āļĢāļąāļšāļ„āđˆāļē

Subscriber āļ„āļ·āļ­āļœāļđāđ‰āļŠāļĄāļąāļ„āļĢāļĢāļąāļšāļ„āđˆāļēāļˆāļēāļ Publisher āļ§āļīāļ˜āļĩāļ—āļĩāđˆāļ™āļīāļĒāļĄāļ—āļĩāđˆāļŠāļļāļ”āđƒāļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ Subscriber āļ„āļ·āļ­āđ€āļĄāļ˜āļ­āļ” sink āļ‹āļķāđˆāļ‡āļĢāļąāļš closure āļŠāļ­āļ‡āļ•āļąāļ§: āļ•āļąāļ§āļŦāļ™āļķāđˆāļ‡āļŠāļģāļŦāļĢāļąāļš error āļŦāļĢāļ·āļ­āļāļēāļĢāļŠāļīāđ‰āļ™āļŠāļļāļ” āđāļĨāļ°āļ­āļĩāļāļ•āļąāļ§āļŠāļģāļŦāļĢāļąāļšāļ„āđˆāļēāļ—āļļāļāļ„āđˆāļēāļ—āļĩāđˆāļĢāļąāļšāđ€āļ‚āđ‰āļēāļĄāļē:

SubscriberBasics.swiftswift
import Combine

// Variable to store subscriptions
// Without this reference, the subscription would be immediately cancelled
var cancellables = Set<AnyCancellable>()

let publisher = ["Swift", "Combine", "iOS"].publisher

// sink() creates a Subscriber that receives values
publisher
    .sink(
        // Called when the Publisher completes or fails
        receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("✅ Completed successfully")
            case .failure(let error):
                print("❌ Error: \(error)")
            }
        },
        // Called for each emitted value
        receiveValue: { value in
            print("Received: \(value)")
        }
    )
    // store() keeps a reference to the subscription
    .store(in: &cancellables)

// Output:
// Received: Swift
// Received: Combine
// Received: iOS
// ✅ Completed successfully
āļĢāļ°āļ§āļąāļ‡āļāļēāļĢāļĢāļąāđˆāļ§āđ„āļŦāļĨāļ‚āļ­āļ‡āļŦāļ™āđˆāļ§āļĒāļ„āļ§āļēāļĄāļˆāļģ

āļ•āđ‰āļ­āļ‡āđ€āļāđ‡āļš AnyCancellable āļ—āļĩāđˆ sink() āļ„āļ·āļ™āļāļĨāļąāļšāļĄāļēāđ€āļŠāļĄāļ­ āļŦāļēāļāđ„āļĄāđˆāļĄāļĩāļāļēāļĢāļ­āđ‰āļēāļ‡āļ­āļīāļ‡ āļāļēāļĢāļŠāļĄāļąāļ„āļĢāļˆāļ°āļ–āļđāļāļĒāļāđ€āļĨāļīāļāļ—āļąāļ™āļ—āļĩāđāļĨāļ°āļˆāļ°āđ„āļĄāđˆāđ„āļ”āđ‰āļĢāļąāļšāļ„āđˆāļēāđƒāļ” āđ†

āļāļēāļĢāđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ”āđ‰āļ§āļĒ Operators

Operators āļ„āļ·āļ­āļŦāļąāļ§āđƒāļˆāļ‚āļ­āļ‡ Combine āļ—āļģāđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āđāļ›āļĨāļ‡ āļāļĢāļ­āļ‡ āđāļĨāļ°āļĢāļ§āļĄāļŠāļ•āļĢāļĩāļĄāļ‚āđ‰āļ­āļĄāļđāļĨāđ„āļ”āđ‰āđāļšāļš declarative Operator āđāļ•āđˆāļĨāļ°āļ•āļąāļ§āļ„āļ·āļ™ Publisher āđƒāļŦāļĄāđˆāļ­āļ­āļāļĄāļē āļ—āļģāđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ™āļģāļĄāļēāļ•āđˆāļ­āđ€āļŠāļ·āđˆāļ­āļĄāđ€āļ›āđ‡āļ™āļĨāļģāļ”āļąāļšāđ„āļ”āđ‰

Operators āđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļˆāļģāđ€āļ›āđ‡āļ™

Operators āđāļ›āļĨāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļˆāļ°āļ›āļĢāļąāļšāđ€āļ›āļĨāļĩāđˆāļĒāļ™āļ„āđˆāļēāļ—āļļāļāļ„āđˆāļēāļ—āļĩāđˆāļ–āļđāļāļŠāđˆāļ‡āļ­āļ­āļ map āđāļ›āļĨāļ‡āļ„āđˆāļē, flatMap āđāļœāđˆ Publisher āļ—āļĩāđˆāļ‹āđ‰āļ­āļ™āļāļąāļ™ āđāļĨāļ° compactMap āļāļĢāļ­āļ‡āļ„āđˆāļē nil āļ­āļ­āļ:

TransformOperators.swiftswift
import Combine

var cancellables = Set<AnyCancellable>()

// map: transforms each value
// Equivalent to map on arrays
[1, 2, 3, 4, 5].publisher
    .map { $0 * 2 }  // Multiply each number by 2
    .sink { print("Doubled: \($0)") }
    .store(in: &cancellables)
// Output: 2, 4, 6, 8, 10

// compactMap: transforms AND filters out nil
// Useful for optional conversions
["1", "two", "3", "four", "5"].publisher
    .compactMap { Int($0) }  // Convert to Int, ignore failures
    .sink { print("Valid number: \($0)") }
    .store(in: &cancellables)
// Output: 1, 3, 5

// flatMap: flattens nested Publishers
// Essential for chaining async operations
struct User { let id: Int; let name: String }

func fetchUser(id: Int) -> AnyPublisher<User, Never> {
    // Simulate an API call
    Just(User(id: id, name: "User \(id)"))
        .delay(for: .milliseconds(100), scheduler: RunLoop.main)
        .eraseToAnyPublisher()
}

[1, 2, 3].publisher
    .flatMap { id in fetchUser(id: id) }  // Each ID becomes an API call
    .sink { user in print("User: \(user.name)") }
    .store(in: &cancellables)

Operators āļāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨ

Operators āļāļĢāļ­āļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļ„āļ§āļšāļ„āļļāļĄāļ§āđˆāļēāļ„āđˆāļēāđƒāļ”āļˆāļ°āļœāđˆāļēāļ™āđ€āļ‚āđ‰āļēāđ„āļ›āđƒāļ™āđ„āļ›āļ›āđŒāđ„āļĨāļ™āđŒ āļˆāļķāļ‡āļŠāļģāļ„āļąāļāļ•āđˆāļ­āļāļēāļĢāđ€āļĨāļĩāđˆāļĒāļ‡āļāļēāļĢāļ›āļĢāļ°āļĄāļ§āļĨāļœāļĨāļ—āļĩāđˆāđ„āļĄāđˆāļˆāļģāđ€āļ›āđ‡āļ™āđāļĨāļ°āđ€āļžāļīāđˆāļĄāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļž:

FilterOperators.swiftswift
import Combine

var cancellables = Set<AnyCancellable>()

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

// filter: keeps only values that satisfy the condition
numbers
    .filter { $0 > 2 }  // Keep only numbers > 2
    .sink { print("Filtered: \($0)") }
    .store(in: &cancellables)
// Output: 3, 3, 3, 4, 5, 5

// removeDuplicates: removes consecutive identical values
numbers
    .removeDuplicates()  // Eliminate consecutive duplicates
    .sink { print("Without duplicates: \($0)") }
    .store(in: &cancellables)
// Output: 1, 2, 3, 4, 5

// debounce: waits for a pause before emitting
// Perfect for real-time search
let searchText = PassthroughSubject<String, Never>()

searchText
    .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
    .removeDuplicates()  // Ignore if text hasn't changed
    .sink { query in
        print("Search: \(query)")
        // Launch API call here
    }
    .store(in: &cancellables)

// Simulate rapid typing
searchText.send("S")
searchText.send("Sw")
searchText.send("Swi")
searchText.send("Swift")  // Only "Swift" is emitted after 300ms

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļĢāļ§āļĄ Publisher āļŦāļĨāļēāļĒāļ•āļąāļ§

āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āđƒāļ™āđ‚āļĨāļāļˆāļĢāļīāļ‡āļĄāļąāļāļ•āđ‰āļ­āļ‡āļĢāļ§āļĄāđāļŦāļĨāđˆāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļŦāļĨāļēāļĒāđāļŦāļĨāđˆāļ‡āđ€āļ‚āđ‰āļēāļ”āđ‰āļ§āļĒāļāļąāļ™ Combine āļĄāļĩ Operators āļŦāļĨāļēāļĒāļ•āļąāļ§āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļ§āļēāļ‡āļŠāļ•āļĢāļĩāļĄāļŦāļĨāļēāļĒāđ€āļŠāđ‰āļ™āļ—āļēāļ‡āđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰

CombineLatest āđāļĨāļ° Zip

combineLatest āļˆāļ°āļŠāđˆāļ‡āļ„āđˆāļēāļ­āļ­āļāļ—āļļāļāļ„āļĢāļąāđ‰āļ‡āļ—āļĩāđˆ Publisher āđƒāļ” āđ† āļŠāđˆāļ‡āļ„āđˆāļēāļ­āļ­āļāļĄāļē āđ‚āļ”āļĒāļĢāļ§āļĄāļāļąāļšāļ„āđˆāļēāđƒāļŦāļĄāđˆāļĨāđˆāļēāļŠāļļāļ”āļ‚āļ­āļ‡āļ•āļąāļ§āļ­āļ·āđˆāļ™ āļŠāđˆāļ§āļ™ zip āļĢāļ­āļˆāļ™ Publisher āļ—āļļāļāļ•āļąāļ§āļŠāđˆāļ‡āļ„āđˆāļēāļ­āļ­āļāļĄāļēāļāđˆāļ­āļ™āļˆāļķāļ‡āļˆāļ°āļĢāļ§āļĄāļ„āđˆāļēāļāļąāļ™:

CombiningPublishers.swiftswift
import Combine

var cancellables = Set<AnyCancellable>()

// Simulate a form with validation
let email = CurrentValueSubject<String, Never>("")
let password = CurrentValueSubject<String, Never>("")

// combineLatest: combines the latest values from each Publisher
// Emits on every change from either source
Publishers.CombineLatest(email, password)
    .map { email, password in
        // Validate that email contains @ and password > 6 chars
        let isEmailValid = email.contains("@")
        let isPasswordValid = password.count >= 6
        return isEmailValid && isPasswordValid
    }
    .sink { isFormValid in
        print("Form valid: \(isFormValid)")
    }
    .store(in: &cancellables)

email.send("user@example.com")  // false (password empty)
password.send("123456")          // true (both are valid)

// zip: waits for one value from each Publisher before emitting
// Useful for synchronizing parallel operations
let firstAPI = PassthroughSubject<String, Never>()
let secondAPI = PassthroughSubject<Int, Never>()

Publishers.Zip(firstAPI, secondAPI)
    .sink { stringValue, intValue in
        print("Received pair: \(stringValue), \(intValue)")
    }
    .store(in: &cancellables)

firstAPI.send("Hello")   // No emission, waiting for secondAPI
secondAPI.send(42)       // Emits: ("Hello", 42)
firstAPI.send("World")   // No emission, waiting for secondAPI
secondAPI.send(100)      // Emits: ("World", 100)

Merge āđ€āļžāļ·āđˆāļ­āļĢāļ§āļĄāļŠāļ•āļĢāļĩāļĄāđ€āļ›āđ‡āļ™āļŦāļ™āļķāđˆāļ‡āđ€āļ”āļĩāļĒāļ§

merge āļĢāļ§āļĄ Publisher āļŠāļ™āļīāļ”āđ€āļ”āļĩāļĒāļ§āļāļąāļ™āļŦāļĨāļēāļĒāļ•āļąāļ§āđƒāļŦāđ‰āđ€āļ›āđ‡āļ™āļŠāļ•āļĢāļĩāļĄāđ€āļ”āļĩāļĒāļ§ āļ„āđˆāļēāļĄāļēāļ–āļķāļ‡āļ•āļēāļĄāļĨāļģāļ”āļąāļšāļāļēāļĢāļŠāđˆāļ‡āđ‚āļ”āļĒāđ„āļĄāđˆāļŠāļ™āđƒāļˆāļ§āđˆāļēāļĄāļēāļˆāļēāļ Publisher āļ•āļąāļ§āđƒāļ”:

MergePublishers.swiftswift
import Combine

var cancellables = Set<AnyCancellable>()

// Multiple user notification sources
let pushNotifications = PassthroughSubject<String, Never>()
let localNotifications = PassthroughSubject<String, Never>()
let inAppMessages = PassthroughSubject<String, Never>()

// Merge unifies all streams into one
Publishers.Merge3(pushNotifications, localNotifications, inAppMessages)
    .sink { message in
        // Handle all notifications the same way
        print("📎 Notification: \(message)")
    }
    .store(in: &cancellables)

pushNotifications.send("New message")      // 📎 Notification: New message
localNotifications.send("Reminder: meeting")  // 📎 Notification: Reminder: meeting
inAppMessages.send("Welcome!")              // 📎 Notification: Welcome!

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āđƒāļ™ Combine

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ–āļđāļāļāļąāļ‡āđ„āļ§āđ‰āđƒāļ™āđāļāļ™āļāļĨāļēāļ‡āļ‚āļ­āļ‡ Combine āļŠāļ™āļīāļ” Failure āļ‚āļ­āļ‡ Publisher āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļ„āļ­āļĄāđ„āļžāđ€āļĨāļ­āļĢāđŒāļ•āļĢāļ§āļˆāļŠāļ­āļšāđ„āļ”āđ‰āļ§āđˆāļēāļ—āļļāļ error āļ–āļđāļāļˆāļąāļ”āļāļēāļĢāđāļĨāđ‰āļ§

āļāļĨāļĒāļļāļ—āļ˜āđŒāļāļēāļĢāļāļđāđ‰āļ„āļ·āļ™

Combine āļĄāļĩ Operators āļŦāļĨāļēāļĒāļ•āļąāļ§āļŠāļģāļŦāļĢāļąāļšāļˆāļąāļ”āļāļēāļĢ error: catch āđ€āļžāļ·āđˆāļ­āđāļ—āļ™āļ—āļĩāđˆāļ”āđ‰āļ§āļĒ Publisher āļ­āļ·āđˆāļ™, retry āđ€āļžāļ·āđˆāļ­āļžāļĒāļēāļĒāļēāļĄāļ­āļĩāļāļ„āļĢāļąāđ‰āļ‡ āđāļĨāļ° replaceError āļŠāļģāļŦāļĢāļąāļšāļ„āđˆāļēāļ•āļąāđ‰āļ‡āļ•āđ‰āļ™:

ErrorHandling.swiftswift
import Combine

var cancellables = Set<AnyCancellable>()

enum APIError: Error {
    case networkError
    case invalidResponse
    case serverError(Int)
}

// Simulate an API call that can fail
func fetchData() -> AnyPublisher<String, APIError> {
    Fail(error: APIError.networkError)
        .eraseToAnyPublisher()
}

// retry: retries N times before propagating the error
fetchData()
    .retry(3)  // Try up to 3 times
    .catch { error -> Just<String> in
        // catch: replaces the error with a fallback Publisher
        print("Error after 3 attempts: \(error)")
        return Just("Cached data")  // Fallback value
    }
    .sink(
        receiveCompletion: { _ in },
        receiveValue: { print("Result: \($0)") }
    )
    .store(in: &cancellables)

// replaceError: replaces any error with a fixed value
// Simpler than catch when only a default value is needed
fetchData()
    .replaceError(with: "Error - default value")
    .sink { print("With fallback: \($0)") }
    .store(in: &cancellables)
Never āļāļąāļš Error

āđƒāļŠāđ‰ setFailureType(to:) āđ€āļžāļ·āđˆāļ­āđāļ›āļĨāļ‡ Publisher āļŠāļ™āļīāļ” Never āđƒāļŦāđ‰āļāļĨāļēāļĒāđ€āļ›āđ‡āļ™āļŠāļ™āļīāļ”āļ—āļĩāđˆāļĨāđ‰āļĄāđ€āļŦāļĨāļ§āđ„āļ”āđ‰ āđāļĨāļ°āđƒāļŠāđ‰ replaceError(with:) āļŦāļĢāļ·āļ­ catch āļŠāļģāļŦāļĢāļąāļšāļ—āļīāļĻāļ—āļēāļ‡āļ•āļĢāļ‡āļāļąāļ™āļ‚āđ‰āļēāļĄ

āļĢāļđāļ›āđāļšāļš MVVM āļāļąāļš Combine

Combine āļœāļŠāļēāļ™āđ€āļ‚āđ‰āļēāļāļąāļšāļĢāļđāļ›āđāļšāļš MVVM (Model-View-ViewModel) āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āđ€āļ›āđ‡āļ™āļ˜āļĢāļĢāļĄāļŠāļēāļ•āļī ViewModel āđ€āļ›āļīāļ”āđ€āļœāļĒ Publisher āļ—āļĩāđˆ View āļ„āļ­āļĒāļŠāļąāļ‡āđ€āļāļ• āļ—āļģāđƒāļŦāđ‰āđ€āļāļīāļ”āļāļēāļĢ binding āđāļšāļšāļĢāļĩāđāļ­āļāļ—āļĩāļŸāļĢāļ°āļŦāļ§āđˆāļēāļ‡āļ‚āđ‰āļ­āļĄāļđāļĨāļāļąāļšāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđ€āļŸāļ‹

ViewModel āđāļšāļšāļĢāļĩāđāļ­āļāļ—āļĩāļŸāļ„āļĢāļšāļ–āđ‰āļ§āļ™

āļ™āļĩāđˆāļ„āļ·āļ­āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡ ViewModel āļŠāļģāļŦāļĢāļąāļšāļĢāļēāļĒāļāļēāļĢāļœāļđāđ‰āđƒāļŠāđ‰āļ—āļĩāđˆāļĄāļĩāļāļēāļĢāļ„āđ‰āļ™āļŦāļē āļāļēāļĢāđ‚āļŦāļĨāļ” āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ error:

UserListViewModel.swiftswift
import Combine
import Foundation

// Data model
struct User: Codable, Identifiable {
    let id: Int
    let name: String
    let email: String
}

// ViewModel with reactive state
final class UserListViewModel: ObservableObject {
    // MARK: - Published Properties (observed by SwiftUI)

    @Published var users: [User] = []           // User list
    @Published var searchQuery: String = ""     // Search text
    @Published var isLoading: Bool = false      // Loading state
    @Published var errorMessage: String?        // Optional error message

    // MARK: - Private Properties

    private var cancellables = Set<AnyCancellable>()
    private let userService: UserServiceProtocol

    // MARK: - Computed Properties

    // Filters users based on search query
    var filteredUsers: [User] {
        guard !searchQuery.isEmpty else { return users }
        return users.filter {
            $0.name.localizedCaseInsensitiveContains(searchQuery)
        }
    }

    // MARK: - Initialization

    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
        setupBindings()
    }

    // MARK: - Private Methods

    private func setupBindings() {
        // Observe searchQuery changes
        // debounce prevents too frequent calls
        $searchQuery
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] query in
                // Server-side search logic if needed
                print("Search updated: \(query)")
            }
            .store(in: &cancellables)
    }

    // MARK: - Public Methods

    func loadUsers() {
        isLoading = true
        errorMessage = nil

        userService.fetchUsers()
            .receive(on: DispatchQueue.main)  // Ensure UI updates on main thread
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    if case .failure(let error) = completion {
                        self?.errorMessage = error.localizedDescription
                    }
                },
                receiveValue: { [weak self] users in
                    self?.users = users
                }
            )
            .store(in: &cancellables)
    }
}

Service āļ”āđ‰āļ§āļĒ Combine āđāļĨāļ° URLSession

URLSession āļœāļ™āļ§āļ Combine āđ„āļ§āđ‰āđāļšāļšāđ€āļ™āļ—āļĩāļŸāļœāđˆāļēāļ™ dataTaskPublisher āļ™āļĩāđˆāļ„āļ·āļ­āļ§āļīāļ˜āļĩāļŠāļĢāđ‰āļēāļ‡āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāđ€āļ„āļĢāļ·āļ­āļ‚āđˆāļēāļĒāļ—āļĩāđˆāļ™āļģāļāļĨāļąāļšāļĄāļēāđƒāļŠāđ‰āđ„āļ”āđ‰:

UserService.swiftswift
import Combine
import Foundation

protocol UserServiceProtocol {
    func fetchUsers() -> AnyPublisher<[User], Error>
}

final class UserService: UserServiceProtocol {
    private let baseURL = URL(string: "https://api.example.com")!
    private let session: URLSession
    private let decoder: JSONDecoder

    init(session: URLSession = .shared) {
        self.session = session
        self.decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
    }

    func fetchUsers() -> AnyPublisher<[User], Error> {
        let url = baseURL.appendingPathComponent("users")

        return session.dataTaskPublisher(for: url)
            // Check HTTP status code
            .tryMap { data, response in
                guard let httpResponse = response as? HTTPURLResponse else {
                    throw URLError(.badServerResponse)
                }
                guard 200..<300 ~= httpResponse.statusCode else {
                    throw URLError(.badServerResponse)
                }
                return data
            }
            // Decode JSON to Swift model
            .decode(type: [User].self, decoder: decoder)
            // Erase concrete type to return AnyPublisher
            .eraseToAnyPublisher()
    }
}

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļ—āļģāļ‡āļēāļ™āļĢāđˆāļ§āļĄāļāļąāļš SwiftUI

Combine āļāļąāļš SwiftUI āđ€āļ›āđ‡āļ™āļ„āļđāđˆāļŦāļđāļ—āļĩāđˆāļ—āļĢāļ‡āļžāļĨāļąāļ‡ āļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļī @Published āļ‚āļ­āļ‡ ObservableObject āļˆāļ°āļāļĢāļ°āļ•āļļāđ‰āļ™āļāļēāļĢāļ­āļąāļ›āđ€āļ”āļ• view āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī

View āļ‚āļ­āļ‡ SwiftUI āļāļąāļš ViewModel āļ‚āļ­āļ‡ Combine

āļ™āļĩāđˆāļ„āļ·āļ­āļ§āļīāļ˜āļĩāđ€āļŠāļ·āđˆāļ­āļĄ ViewModel āđ€āļ‚āđ‰āļēāļāļąāļš view āļ‚āļ­āļ‡ SwiftUI:

UserListView.swiftswift
import SwiftUI

struct UserListView: View {
    // StateObject: creates and owns the ViewModel
    @StateObject private var viewModel = UserListViewModel()

    var body: some View {
        NavigationStack {
            Group {
                if viewModel.isLoading {
                    // Centered loading indicator
                    ProgressView("Loading...")
                } else if let error = viewModel.errorMessage {
                    // Error view with retry button
                    VStack(spacing: 16) {
                        Text("Error: \(error)")
                            .foregroundStyle(.red)
                        Button("Retry") {
                            viewModel.loadUsers()
                        }
                    }
                } else {
                    // User list
                    List(viewModel.filteredUsers) { user in
                        UserRowView(user: user)
                    }
                }
            }
            .navigationTitle("Users")
            .searchable(text: $viewModel.searchQuery)  // Direct binding
            .onAppear {
                viewModel.loadUsers()  // Load on first appearance
            }
        }
    }
}

struct UserRowView: View {
    let user: User

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(user.name)
                .font(.headline)
            Text(user.email)
                .font(.subheadline)
                .foregroundStyle(.secondary)
        }
        .padding(.vertical, 4)
    }
}

āļĢāļđāļ›āđāļšāļšāļ‚āļąāđ‰āļ™āļŠāļđāļ‡

āļāļēāļĢāļĒāļāđ€āļĨāļīāļāđāļĨāļ°āļāļēāļĢāļ—āļģāļ„āļ§āļēāļĄāļŠāļ°āļ­āļēāļ”āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ§āļ‡āļˆāļĢāļŠāļĩāļ§āļīāļ•āļ‚āļ­āļ‡āļāļēāļĢāļŠāļĄāļąāļ„āļĢāđ€āļ›āđ‡āļ™āļŠāļīāđˆāļ‡āļŠāļģāļ„āļąāļāđ€āļžāļ·āđˆāļ­āļŦāļĨāļĩāļāđ€āļĨāļĩāđˆāļĒāļ‡ memory leak āļĢāļđāļ›āđāļšāļš cancellables āļ—āļĩāđˆāđƒāļŠāđ‰āļĢāđˆāļ§āļĄāļāļąāļš AnyCancellable āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļāļēāļĢāļ—āļģāļ„āļ§āļēāļĄāļŠāļ°āļ­āļēāļ”āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī:

CancellationPatterns.swiftswift
import Combine

final class DataManager {
    // Set of cancellables: automatically cancelled on destruction
    private var cancellables = Set<AnyCancellable>()

    // Individual cancellable for fine-grained control
    private var currentRequest: AnyCancellable?

    func startPolling() {
        // Timer that emits every 5 seconds
        Timer.publish(every: 5, on: .main, in: .common)
            .autoconnect()  // Starts automatically
            .sink { [weak self] _ in
                self?.fetchLatestData()
            }
            .store(in: &cancellables)
    }

    func fetchLatestData() {
        // Cancel the previous request if it exists
        currentRequest?.cancel()

        currentRequest = URLSession.shared
            .dataTaskPublisher(for: URL(string: "https://api.example.com/data")!)
            .map(\.data)
            .decode(type: [String].self, decoder: JSONDecoder())
            .replaceError(with: [])
            .receive(on: DispatchQueue.main)
            .sink { data in
                print("Data received: \(data)")
            }
    }

    deinit {
        // All cancellables are automatically cancelled
        print("DataManager destroyed, subscriptions cancelled")
    }
}

Schedulers āļŠāļģāļŦāļĢāļąāļš threading

Schedulers āļ„āļ§āļšāļ„āļļāļĄāļ§āđˆāļēāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢāļˆāļ°āļ–āļđāļāļĢāļąāļ™āļšāļ™ thread āđƒāļ” āđƒāļŠāđ‰ subscribe(on:) āļŠāļģāļŦāļĢāļąāļšāļ‡āļēāļ™āđ€āļšāļ·āđ‰āļ­āļ‡āļŦāļĨāļąāļ‡āđāļĨāļ° receive(on:) āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ­āļąāļ›āđ€āļ”āļ• UI:

SchedulerPatterns.swiftswift
import Combine
import Foundation

var cancellables = Set<AnyCancellable>()

func loadAndProcessData() -> AnyPublisher<ProcessedData, Error> {
    URLSession.shared.dataTaskPublisher(for: apiURL)
        // Perform parsing on a background thread
        .subscribe(on: DispatchQueue.global(qos: .userInitiated))
        .map(\.data)
        .decode(type: RawData.self, decoder: JSONDecoder())
        // Heavy processing on background thread
        .map { rawData in
            // This expensive operation runs in the background
            processData(rawData)
        }
        // Return to main thread for UI
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

āļšāļ—āļŠāļĢāļļāļ›

Combine āļĄāļ­āļšāđāļ™āļ§āļ—āļēāļ‡āļ—āļĩāđˆāļ—āļĢāļ‡āļžāļĨāļąāļ‡āđāļĨāļ°āđ€āļ›āđ‡āļ™āđāļšāļš declarative āđƒāļ™āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļŠāļ•āļĢāļĩāļĄāļ‚āđ‰āļ­āļĄāļđāļĨ asynchronous āđƒāļ™āđāļ­āļ› iOS āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļŠāļģāļ„āļąāļ:

✅ Publishers āļŠāđˆāļ‡āļ„āđˆāļēāļ­āļ­āļāļĄāļēāļ•āļēāļĄāđ€āļ§āļĨāļē ✅ Subscribers āļĢāļąāļšāđāļĨāļ°āļ›āļĢāļ°āļĄāļ§āļĨāļœāļĨāļ„āđˆāļēāđ€āļŦāļĨāđˆāļēāļ™āļąāđ‰āļ™ âœ… Operators āđāļ›āļĨāļ‡āđāļĨāļ°āļĢāļ§āļĄāļŠāļ•āļĢāļĩāļĄāđ€āļ‚āđ‰āļēāļ”āđ‰āļ§āļĒāļāļąāļ™ âœ… AnyCancellable āļˆāļąāļ”āļāļēāļĢāļ§āļ‡āļˆāļĢāļŠāļĩāļ§āļīāļ•āļ‚āļ­āļ‡āļāļēāļĢāļŠāļĄāļąāļ„āļĢ âœ… @Published āļāļąāļš SwiftUI āļŠāļĢāđ‰āļēāļ‡āļāļēāļĢ binding āđāļšāļšāļĢāļĩāđāļ­āļāļ—āļĩāļŸāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī ✅ Schedulers āļ„āļ§āļšāļ„āļļāļĄ threading āđ€āļžāļ·āđˆāļ­āđƒāļŦāđ‰āđ„āļ”āđ‰āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŠāļđāļ‡āļŠāļļāļ”

āļāļēāļĢāđ€āļŠāļĩāđˆāļĒāļ§āļŠāļēāļ Combine āđ€āļ›āļīāļ”āļ—āļēāļ‡āļŠāļđāđˆāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđāļ­āļ› iOS āļ—āļĩāđˆāđāļ‚āđ‡āļ‡āđāļāļĢāđˆāļ‡ āļ”āļđāđāļĨāļĢāļąāļāļĐāļēāļ‡āđˆāļēāļĒ āđāļĨāļ°āļ•āļ­āļšāļŠāļ™āļ­āļ‡ āļāļēāļĢāļœāļŠāļēāļ™āļĢāļ§āļĄāđ€āļ‚āđ‰āļēāļāļąāļš SwiftUI āđƒāļ™āļĢāļ°āļ”āļąāļšāđ€āļ™āļ—āļĩāļŸāļ—āļģāđƒāļŦāđ‰ Combine āļāļĨāļēāļĒāđ€āļ›āđ‡āļ™āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­āļŠāļģāļ„āļąāļāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļžāļąāļ’āļ™āļē iOS āļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆ

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āđāļ—āđ‡āļ

#combine
#swift
#ios
#reactive
#async

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļāļēāļĢāļĒāđ‰āļēāļĒāļˆāļēāļ Combine āđ„āļ›āļĒāļąāļ‡ async/await āđƒāļ™ Swift āļžāļĢāđ‰āļ­āļĄāļĢāļđāļ›āđāļšāļšāļāļēāļĢāļ­āļĒāļđāđˆāļĢāđˆāļ§āļĄāļāļąāļ™

Combine vs async/await āđƒāļ™ Swift: āļĢāļđāļ›āđāļšāļšāļāļēāļĢāļĒāđ‰āļēāļĒāļĢāļ°āļšāļšāđāļšāļšāļ„āđˆāļ­āļĒāđ€āļ›āđ‡āļ™āļ„āđˆāļ­āļĒāđ„āļ›

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĒāđ‰āļēāļĒāļˆāļēāļ Combine āđ„āļ›āļĒāļąāļ‡ async/await āđƒāļ™ Swift: āļāļĨāļĒāļļāļ—āļ˜āđŒāđāļšāļšāļ„āđˆāļ­āļĒāđ€āļ›āđ‡āļ™āļ„āđˆāļ­āļĒāđ„āļ› āļĢāļđāļ›āđāļšāļšāļāļēāļĢāđ€āļŠāļ·āđˆāļ­āļĄāđ‚āļĒāļ‡ āđāļĨāļ°āļāļēāļĢāļ­āļĒāļđāđˆāļĢāđˆāļ§āļĄāļāļąāļ™āļ‚āļ­āļ‡āļāļĢāļ°āļšāļ§āļ™āļ—āļąāļĻāļ™āđŒāđƒāļ™āđ‚āļ„āđ‰āļ”āđ€āļšāļŠ iOS

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ iOS: VoiceOver āđāļĨāļ° Dynamic Type

āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡ iOS āđƒāļ™āļ›āļĩ 2026: VoiceOver āđāļĨāļ° Dynamic Type

āđ€āļ•āļĢāļĩāļĒāļĄāļ•āļąāļ§āļŠāļąāļĄāļ āļēāļĐāļ“āđŒ iOS āļ”āđ‰āļ§āļĒāļ„āļģāļ–āļēāļĄāļŠāļģāļ„āļąāļāđ€āļĢāļ·āđˆāļ­āļ‡āļāļēāļĢāđ€āļ‚āđ‰āļēāļ–āļķāļ‡: VoiceOver, Dynamic Type, traits āđ€āļŠāļīāļ‡āļ„āļ§āļēāļĄāļŦāļĄāļēāļĒ āđāļĨāļ°āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš.

Metaprogramming āļ”āđ‰āļ§āļĒ Swift Macros āđāļĨāļ°āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ‚āļ„āđ‰āļ”āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļī

Swift Macros: āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļīāļ‚āļ­āļ‡ metaprogramming

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļ‚āļ­āļ‡ Swift Macros: āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļĄāļēāđ‚āļ„āļĢ freestanding āđāļĨāļ° attached, āļāļēāļĢāļˆāļąāļ”āļāļēāļĢ AST āļ”āđ‰āļ§āļĒ swift-syntax āđāļĨāļ°āļ•āļąāļ§āļ­āļĒāđˆāļēāļ‡āđ€āļŠāļīāļ‡āļ›āļāļīāļšāļąāļ•āļīāđ€āļžāļ·āđˆāļ­āļāļģāļˆāļąāļ”āđ‚āļ„āđ‰āļ”āļ‹āđ‰āļģāļ‹āļēāļ