For a Store/Factory/ViewModel pattern using Combine and SwiftUI, I'd like a Store protocol-conforming class to expose a publisher for when specified model object(s) change internal properties. Any subscribed ViewModels can then trigger objectWillChange to display the changes.
(This is necessary because changes are ignored inside a model object that is passed by reference, so @Published/ObservableObject won't auto-fire for Factory-passed Store-owned models. It works to call objectWillChange in the Store and the VM, but that leaves out any passively listening VMs.)
That's a delegate pattern, right, extending @Published/ObservableObject to passed-by-reference objects? Combing through combine blogs, books, and docs hasn't triggered an idea to what's probably a pretty standard thing.
Crudely Working Attempt
I thought PassthroughSubject<Any,Never> would be useful if I exposed a VM's objectWillChange externally, but PassthroughSubject.send() will fire for every object within the model object. Wasteful maybe (although the ViewModel only fires its objectWillChange once).
Attaching a limiter (e.g., throttle, removeDuplicates) on Ext+VM republishChanges(of myStore: Store) didn't seem to limit the .sink calls, nor do I see an obvious way to reset the demand between the PassthroughSubject and the VM's sink... or understand how to attach a Subscriber to a PassthroughSubject that complies with the Protcols. Any suggestions?
Store-Side
struct Library {
   var books: // some dictionary
}
class LocalLibraryStore: LibraryStore {
    private(set) var library: Library {
         didSet { publish() }
    }
    var changed = PassthroughSubject<Any,Never>()
    func removeBook() {}
}
protocol LibraryStore: Store { 
    var changed: PassthroughSubject<Any,Never> { get }
    var library: Library { get }
}
protocol Store {
    var changed: PassthroughSubject<Any,Never> { get }
}
extension Store {
    func publish() {
        changed.send(1)
        print("This will fire once.")
    }
}
VM-Side
class BadgeVM: VM {
    init(store: LibraryStore) {
        self.specificStore = store
        republishChanges(of: jokesStore)
    }
    var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
    internal var subscriptions = Set<AnyCancellable>()
    @Published private var specificStore: LibraryStore
    var totalBooks: Int { specificStore.library.books.keys.count }
}
protocol VM: ObservableObject {
    var subscriptions: Set<AnyCancellable> { get set }
    var objectWillChange: ObservableObjectPublisher { get set }
}
extension VM {
    internal func republishChanges(of myStore: Store) {
        myStore.changed
            // .throttle() doesn't silence as hoped
            .sink { [unowned self] _ in
                print("Executed for each object inside the Store's published object.")
                self.objectWillChange.send()
            }
            .store(in: &subscriptions)
    }
}
class OtherVM: VM {
    init(store: LibraryStore) {
        self.specificStore = store
        republishChanges(of: store)
    }
    var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
    internal var subscriptions = Set<AnyCancellable>()
    @Published private var specificStore: LibraryStore
    var isBookVeryExpensive: Bool { ... }
    func bookMysteriouslyDisappears() { 
         specificStore.removeBook() 
    }
}
 
    