Introduction
Software architecture in iOS is a topic that never quite settles. Not because we lack patterns or principles1, but because of the sheer number of interpretations, adaptations, and proposals that coexist at the same time.
A quick search yields hundreds of articles about architecture in SwiftUI: Clean Architecture, MVVM, MVVM variants, new acronyms, reinterpretations of well-known concepts… often starting from the same sources and arriving at different solutions. Not always contradictory, but leading to a fragmented and overwhelming landscape, difficult to reconcile for anyone trying to develop their own judgment.
This series explores an approach that can work well in SwiftUI, based on practical experience rather than claiming a single correct architecture or proposing new acronyms.
The goal is to illustrate ways of thinking about architecture and provide tools to make informed decisions based on the context and priorities of your project.
On principles, practices, and context
It is common to read categorical statements about best practices: what should always be done, what should never be done, what violates this or that principle.
While these claims often have solid theoretical foundations, they are frequently presented without the context that makes the principle meaningful.
Software engineering principles are not moral rules; they are responses to concrete problems. If the problem does not exist, applying the solution preemptively can introduce unnecessary complexity.
In practice, this often translates into following fixed structures: one ViewModel per screen, one layer per concept, an architecture defined upfront and replicated regardless of the nature of the feature.
This approach has organizational advantages, especially in large teams, but it also has a cost: more code, more moving parts, more surface area for errors, and greater difficulty reasoning about the system.
I have worked on projects where every new feature automatically started with VIPER2 even when the actual complexity did not justify it. The pattern functioned as a social contract within the team, but the result was software that did more than necessary to solve the problem at hand.
What guides this series
If we think of a project as a sequence of needs that evolve over time, the “correct” code is the one that satisfies present needs without unnecessarily closing doors.
This does not imply a lack of structure or principles. On the contrary, it requires being upfront about the goals we want to achieve and allowing the architecture to emerge as a consequence of them, rather than imposing it from the start.
In the following chapters, we will define those goals3 and start from a deliberately simple example that satisfies business requirements. Changes will be introduced only when the code begins to collide with those goals and requirements evolve4.
What follows is not a universal recipe. It is an argued opinion, based on practical experience, and presented as such. Feel free to take what you find useful and share your perspective in the comments.