The @Republished proprty wrapper allows an ObservableObject nested
within another ObservableObject to naturally notify SwiftUI of changes.
Some architectural patterns split view logic, from presentation logic, from domain logic. Often with this separation of concerns the developer ends up having to set up manual data bindings between the ViewModel (or Presenter) and the underlying DomainModel (or business logic). 'Reactivity' is hard — and tools used to handle it directly, like RxSwift and Combine, are error prone.
With @Republished you can lean into SwiftUI's regular change observation pipeline and code imperatively — while still keeping your model abstraction layer crisp for your business essential logic.
struct MyView: View {
@StateObject var viewModel: MyViewModel
var body: some View {
Text(viewModel.myTitle)
}
}
final class MyViewModel: ObservableObject {
/// Without Republished SwiftUI would be unaware of changes.
@Republished var domainModel: MyDomainModel
var myTitle: String {
"\(domainModel.beerBottles.count) bottles of beer on the wall!"
}
}
final class MyDomainModel: ObservableObject {
/// Essential business logic is kept clean and testable.
@Published var beerBottles: BeerBottle
func takeOneDown() {
beerBottles.removeLast()
}
}Nested ObservableObjects don't play well with SwiftUI.
An ObservableObject is an identity type, not a value type. This means a field on an outer ObservableObject containing an inner ObservableObject doesn't change when the inner object changes. This means SwiftUI isn't notified of any changes happening and won't update corresponding views.
This package solves this problem by subscibing to the inner object and republishing its change notifications.
Use this library via Swift Package Manger by adding a dependency in your Package.swift.
.package(url: "https://github.com/adam-zethraeus/Republished", from: "1.1.1")The outer ObservableObject should hold the inner one in a var annotated
with the @Republished property wrapper.
@Republished private var inner: InnerObservableObjectThe inner ObservableObject's objectWillChange notifications will be
re-emitted by the outer ObservableObject, allowing it to provide SwiftUI
compatible accessors derived from the inner.
final class OuterObservableObject: ObservableObject {
/* ... */
@Republished private var inner: InnerObservableObject
var infoFromInner: String { "\(inner.info)" }
}You can also use @Republished with an optional ObservableObject or an
array of ObservableObjects.
(Note that regular @StateObject and @ObservedObject do not allow this.)
final class OuterObservableObject: ObservableObject {
@Republished private var inner: InnerObservableObject
@Republished private var optionalInner: InnerObservableObject?
@Republished private var innerList: [InnerObservableObject]
}The outer ObservableObject will only republish notifications from accessed fields.
If an inner ObservableObject is never touched it will not be subscribed to. (It also
can't be affecting a view if it's not read.)
The RepublishedExampleApp contains an example of an inner ObservableObject domain model, used by an outer ObservableObject view model in a regular SwiftUI View.
It also shows how to use @Republished with optionals and arrays of inner observable objects.
This library was inspired by some of the challenges described in a blog post by @mergesort introducing their the data store library Boutique.