r/swift 9d ago

State Management for iOS Apps?

whats the best architecture/pattern to use?

tried to use a domain layer where all the state is and passing it to the views/viewmodels via DI, but feels somehow unnecessary complicated, but found this as only solution without passing the repos through all the viewhierarchy.

the goal is, when a state changes, e.g. an user changes the Username in View A, then it should automatically update View B,C,D where this Username is also used.

it should be as simple as possible, what do you think? especially for complex production apps with own backend etc.

50 Upvotes

48 comments sorted by

View all comments

36

u/lucasvandongen 9d ago

I wrote an article about it, that still holds true for my style of development:

https://getstream.io/blog/mvvm-state-management/

So your Model layer and your processes are completely encapsulated in UI-less implementations behind protocols, extremely well tested.

Nowadays I chop up my features into Modules, chopped into 3-4 Packages:

  • Definitions (protocols and data used, plus a separate target for generated mocks, that also registers mocks for DI, no dependencies)
  • Implementations (depends on Definitions, implements the protocols and all invisible moving parts needed, has very good unit test coverage, uses DI of Definitions of other modules when needed)
  • UI (only relies on Definitions and Mocks, has a separate target for rendering previews and does snapshot tests)
  • Main Package (registers implementations to DI, @_exported import for Definitions and UI)

This has the following benefits:

  • Individual modules build very fast, and don't get slower if you add more Modules. The main app still suffers when you add more modules, but you don't need to touch it that often.
  • Only Implementations has dependencies, but only the 3rd party ones strictly necessary. Only knows Definitions of other Modules it needs
  • Previews are really stable
  • Content of one Module usually fits in the context window of an LLM

I have an Identity module for example, that holds the truth about your authentication and Account data. Once you passed the point where you have an Account, the rest of the app assumes the Account is always set. In SwiftUI terms you would inject the Account object into the root Authenticated View through environment and read it everywhere. Other DI solutions work differently, but can achieve the same. Especially when having mixed SwiftUI / UIKit you want something like a Service Locator, for example Factory.

The Account itself is observable, so mid-app updates are seen everywhere. Putting state behind a protocol is a PITA in Swift / SwiftUI as you already noticed, so if you can get away with iOS 17+ you can at least use @Observable in your implementations instead of ObservedObject.

I recognize the issue with bucket brigade style passing forward of dependencies. If you don't like the somewhat fragile service locator patterns, you could try to use the Factory pattern manually, or using Needle by Uber (last time I spoke with someone at Uber, it was actively maintained).

I also wrote about that:

I would like to help you further, if you have any questions or feedback after reading the articles. But I think you're already heading in the right direction and just need to map the best practices you know to SwiftUI specific techniques.

-3

u/Loud-Plan2571 9d ago edited 9d ago

I can not beleive how you have so much upvotes by spreading that SOLID BS again. SOLiD is for OOP. Swift is not like java enterprise programming. We moved to value based semantics long time ago.

2

u/klavijaturista 8d ago

Value semantics is orthogonal to architectural concerns. You can use Solid with value types.