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: InnerObservableObject
The 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
.