Publishable - synchronous observation of Observable changes through Combine

I'm excited to share with you the second package I'm releasing this week - Publishable

Excerpts from README:


What Problem Publishable Solves?

With the introduction of SE-0475: Transactional Observation of Values, Swift gains 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 Publishable 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. It even works with the SwiftData.Model macro!

import Publishable 

@Publishable @Observable
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

How Publishable Works?

The @Publishable macro relies on two key properties of Swift Macros and Observation module:

  • Macro expansions are compiled in the context of the module where they’re used. This allows references in the macro to be overloaded by locally available symbols.
  • Swift exposes ObservationRegistrar as a documented, public API, making it possible to use it safely and directly.

Publishable leverages these facts to overload the default ObservationRegistrar with a custom one that:

  • Forwards changes to Swift’s native ObservationRegistrar
  • Simultaneously emits values through generated Combine publishers

While I acknowledge that this usage might not have been intended by the authors, I would refrain from calling it a hack. It relies solely on well-understood behaviors of Swift and its public APIs.

This approach has been carefully tested and verified to work with both Observable and SwiftData.Model macros.


For now, this macro only works with final classes - though I believe support for non-final classes should be feasible as well.

What excites me most, however, is what I want to build next with Publishable: "lazy" computed properties - that is, computed properties whose values are automatically cached and invalidated when needed.

It’s extremely convenient to define operations on collections (like filtering or sorting) as computed properties, but this can have negative performance implications, especially in the context of SwiftUI view updates. This feature would preserve that convenience for developers while scaling efficiently with larger data sets.

If you’d like to see support for non-final classes (which isn’t my personal priority at the moment), or if you have any questions or suggestions - let me know!

I encourage you to check out the implementation on GitHub.

6 Likes