hmlongco / Factory

A new approach to Container-Based Dependency Injection for Swift and SwiftUI.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Some minor suggestions and issues.

LeoTheCatMeow opened this issue · comments

commented

Thanks for making this awesome library! I have some small suggestions and issues, not sure if anyone has brought it up yet.

  1. The struct name "Factory" conflicts with module name "Factory", and thus there is no way to refer to another type in the module. "Factory.Scope" does not work, and would require some messy typealias to work around. "Resolver" had the same issue. Is there any plan to address this issue?

  2. I see InjectedObject is now using @StateObject internally, while the previous Resolver library was using @ObservedObject. I get quite some warnings about accessing @StateObject before it being installed on a View, and had to thus make my own custom wrapper. I am not 100% sure on this but, wouldn't @ObservedObject be a little lighter since SwiftUI does not attempt to allocate storage for it? @StateObject is more resistant to constant View refresh but it does have limits of not being as accessible.

  3. Another usage that I ran into and thus have to make custom wrappers for, is where I have quite a big view model with many sub view models (due to SwiftUI's View refresh logic, before iOS17 is a thing we kinda have to divide things into many sub view models), a lot of times the big view model is registered, but I don't want to register all the sub view models, since they are just contained by the big view model anyway. So I often find the need to resolve a service and then access a "sub-service" inside it, so I made a custom wrapper to support an additional keypath to grab the sub-service. Is this a reasonable design or is this anti-pattern?

At this date Factory is the name of the import and wrapper and I don't see that changing as it would be a fairly significant breaking change. It could be possible to add another library name to the mix. Have to check. Easier would be simply to typealias a few things for better visibility (FactoryScope = Scope).

InjectedObject should only be used in SwiftUI views. Resolver did use ObservedObject, but that also meant the service needed to be correctly scoped (shared?). SO avoids this.

As to the later point, nesting VM's is definitely an anti-pattern. SwiftUI makes view composition pretty easy, and I'd be doing my best to break up massive views (and VMs) into smaller ones.

commented

@hmlongco For the 3rd point, I understand SwiftUI makes view composition pretty easy. But if you have a complicated view model, that is observed by many views, one change in the view model will refresh too many views. For instance, I have a video player view model, it may need to manage video loading states, video control states, video progress states, and many more. If progress is updated every player loop, that will trigger the view model to objectWillChange non-stop. So I instead change the video player view model to a video player coordinator, which coordinates multiple sub view models. We have 1 sub view model to manage video frame size, 1 sub view model to manage control status, 1 sub view model to manage progress etc... This way we don't refresh too many views at the same time. Now obviously these sub view models are managed by the coordinator, they belong to the coordinator, so I only want to resolve for the coordinator, not all the sub view models. Once I get the coordinator, then I access the subview model I need, this way a view does not get affected by unrelated state changes. An example usage would be like:

@InjectedObject(\.videoPlayerCoordinator, subPath:\.videoProgressViewModel) var videoProgressViewModel

Is my thought process reasonable here, or should I structure things differently?

As to the later point, I'd try to structure things differently if possible, but that's just me. I definitely wouldn't do the InjectedObjects of InjectedObjects. (In fact, I'm actually a bit sorry I relented and created InjectedObject.)

I just published 2.3.1 and added some aliases for common objects FactoryScope for Scope, FactoryContainer for Container, etc..

commented

@hmlongco Great! Thanks for the new additions. Yeah I know it is an ugly solution and I should re-structure it. But with the new Factory Library, I can access the target dependency directly with the factory + call as function approach, and then i can access any contained value as needed. So after I transition to the new factory syntax, things would be much smoother.
This whole ugly thing of

@InjectedObject(\.videoPlayerCoordinator, subPath:\.videoProgressViewModel) var videoProgressViewModel

Can be changed to

@StateObject var videoProgressViewModel = Container.videoPlayerCoordinator().videoProgressViewModel

With the added benefit of using either @StateObject or @ObseredObject or even @State based on what I need. Quite nice. I just gotta refactor the code and move to new way. Thanks again!

commented

@hmlongco Sorry to open up this closed issue again. When you have time later or when this library gets another update later, could you add "public" to all the aliases you added in 2.3.1? Thanks for adding the aliases but they are using the default internal scope and can't be accessed from outside at all XD

These were pushed in 2.3.2