Oh but TDD *is* a genuine need for an alternative implementation. I don't mind that. I just argue that protocol extraction should be done immediately before adding those tests, and not in hope of maybe adding tests in the future.
I think that's perfectly fine code. Apple does almost exactly that in the POP talk to make the
Renderer
class they create testable. Again, no problem as long as the abstraction is actually used
Yeah, I think my confusion is that in my mind, TDD means you
always have that protocol (since you are writing the tests against an unimplemented protocol before you write the implementation). So it was a little hard for me to try to guess where you drew the line. Sounds like we aren’t that different in mindset here. My mindset is that POP effectively is steering folks towards TDD-like behaviors, but if they aren’t leveraging it, it is a shame and a waste of energy.
Hmm. How do you make only a small subset of the model available to the view?
There’s not much enforcement in the language itself, for sure. This is more a “discipline” thing in my case.
But for the most part, my views don’t use @EnvironmentObject often. Instead it all gets passed in. So I’m either passing in a View Model or a Core Data object that the view represents most of the time. The type of project I am working on makes this a natural approach. So a view isn’t going to be wandering around the object graph and poking at things. Code that applies transformations to the object graph doesn’t live within it, so a view model is effectively mandatory in my architecture for any UI that wants to make changes to the model, to get at the parts of the editor workflow that doesn’t exist in the object graph.
Yeah, Combine is a good match for this use case. For changes that affect the views, though, changes in the model listened in the view model would have to be re-published to a @Published property so the view could update... I'm not convinced.
True, but this is why there’s an .assign() that explicitly takes the @Published property’s publisher, and doesn’t even need you to store anything in an AnyCancellable. So the update binding from the model is literally a line of code in an initializer.
My view models use this approach for binding individual properties to the Core Data object backing the view model. I came to this approach partly because of your own suggestion that with Core Data, you should just deal with the fact that properties are optional. Which this lets me do rather elegantly without leaking things like the default displayable value into the model. Below is something similar to what I have setup as a view model for items that are displayed in List/LazyVGrid. It also has the neat side effect that I can use this to effectively type-erase dataObject if I need it to, and support some rather different Core Data entities if I want, rather than making those entities conform to a protocol that starts handling things the View Model should really own, IMO.
Swift:
class CollectionItemViewModel: ObservableObject {
@Published var title: String = "<<TITLE>>"
@Published var subtitle: String = "<<SUBTITLE>>"
@Published var thumbnail: UIImage? // Don’t actually do this, especially in LazyVGrid.
init(_ dataObject: MyCoreDataObject) {
// KVO Publishers
dataObject.publisher(for: \.albumName).compactMap({$0}).assign(to: &$title)
dataObject.publisher(for: \.albumArtist).compactMap({$0}).assign(to: &$subtitle)
// Custom Publisher
dataObject.lazyThumbnailPublisher().assign(to: &$thumbnail)
}
}
FYI, using this pattern also means you can subscribe UI to actors
without having to manage the isolation contexts except when hooking up the subscriber. So I can have shared state like a download queue live inside an actor, but also have the ability to update the UI with the queue’s state from a ViewModel using very little code. Literally just need to wrap the subscription code in a Task and add await in the appropriate spot. The alternative is spinning up a long-lived Task that watches an AsyncSequence of updates, and managing that yourself.
I thought about breaking up my ObservableObject into smaller objects. So far that's my preferred route, but I'll have to sit down and carefully examine the options. I'm planning a refactor of my side-project app into two separate modules when I finish the updates to the renderer I've been working on. Basically I will try to extract the renderer itself to a different module, so it can be used like one would use a SceneKit or ARKit view, to completely decouple the renderer from the app itself. That should also allow me to practice with some of the things mentioned in this thread (better use protocols, handle the massive-observableObject problem, maybe even add tests). And DocC. I want to try DocC.
That’s generally what I’m doing as well, since it means that at least in my cases, I am passing one specific object to a view, and environmentObject gets regulated to more “global” scope objects that actually need to be accessed from random parts of the API. Things like the download queue I mentioned above.
Ooh. Good catch. I didn't know that could happen.
Using the actor playground we talked about earlier, I did mention it was non-deterministic behavior when it came to ordering, because of things executing concurrently. I just didn’t actually take my own learnings into account. Whoops.