Skip to content

NSFatalError/Relay

Repository files navigation

Relay

Codecov

Essential tools that extend the capabilities of Observation.

Contents

Relayed and Publishable

Observe changes to Observable types synchronously with Combine.

With the introduction of Observations, Swift gained built-in support for observing changes to Observable types. This solution is great, but it only covers some of the use cases, as it publishes the updates via an AsyncSequence.

In some scenarios, however, developers need to perform actions synchronously - immediately after a change occurs.

This is where the Publishable protocol comes in. It allows Observation and Combine to coexist within a single type, letting you take advantage of the latest Observable features while processing changes synchronously when needed. Classes can gain Publishable conformance by attaching either the @Relayed or @Publishable macro:

import Relay 

@Relayed
final class Person {
    var name = "John"
    var surname = "Doe"
    
    var fullName: String {
        "\(name) \(surname)"
    }
}

let person = Person()
let nameCancellable = person.publisher.name.sink { name in
    print("Name -", name)
}
let fullNameCancellable = person.publisher.fullName.sink { fullName in
    print("Full name -", fullName)
}

// Initially prints (same as `Published` property wrapper):
// Name - John
// Full name - John Doe

person.name = "Kamil"
// Prints:
// Name - Kamil
// Full name - Kamil Doe

person.surname = "Strzelecki"
// Prints:
// Full name - Kamil Strzelecki

Memoized

Perform expensive computations lazily and cache their outputs until Observable inputs change.

Computed properties in Swift are a great way of getting an always-up-to-date values derived from other properties of a type. However, they can also hide away computational complexity from the caller, who might assume that accessing them is trivial and therefore call them repeatedly.

With the conveniences afforded by SwiftUI and Observation, it’s easy to fall into this trap by performing expensive computations, like mapping or filtering a collection, every time View.body is accessed:

@MainActor @Observable
final class ViewModel {
    var data = [String]()
    var query: String?

    var filteredData: [String] {
        print("recompute")
        guard let query else {
            return data
        }
        return data.filter { 
            $0.localizedCaseInsensitiveContains(query)
        }
    }
}

let model = ViewModel()
model.filteredData // Prints: recompute
model.filteredData // Prints: recompute

model.data = [...]
model.filteredData // Prints: recompute
model.filteredData // Prints: recompute

model.data = [...]
model.query = "..."
model.filteredData // Prints: recompute
model.filteredData // Prints: recompute

In the example above, it’s clear that we could save computing resources on repeated access to filteredData when both query and data remain unchanged. The @Memoized macro allows you to do exactly that by automatically caching and updating values derived from their underlying Observable inputs.

To use it, refactor your computed property into a method and apply the @Memoized macro to it:

@MainActor @Observable
final class ViewModel {
    var data = [String]()
    var query: String?

    @Memoized("filteredData")
    private func filterData() -> [String] {
        print("recompute")
        guard let query else {
            return data
        }
        return data.filter { 
            $0.localizedCaseInsensitiveContains(query)
        }
    }
}

let model = ViewModel()
model.filteredData // Prints: recompute
model.filteredData

model.data = [...]
model.filteredData // Prints: recompute
model.filteredData

model.data = [...]
model.query = "..."
model.filteredData // Prints: recompute
model.filteredData

Documentation

Full documentation is available on the Swift Package Index.

Installation

.package(
    url: "https://github.com/NSFatalError/Relay",
    from: "3.0.0"
)

About

Essential tools that extend the capabilities of Observation

Topics

Resources

License

Stars

Watchers

Forks

Languages