How do you structure modules in large iOS apps?
The core goals modularization is actually solving
- Parallel/incremental compilation
- Enforced boundaries
- Team ownership alignment
- Testability in isolation
- Reusability
Common structural approaches
1. Layered/horizontal modularization — Core, Networking, Persistence, DesignSystem, Features, App.
2. Feature-based/vertical modularization — FeatureProfile, FeatureCheckout, FeatureOnboarding, each with a narrow public interface.
3. Layered + feature-based combined (the realistic large-scale shape)
Critical rule: features depend on shared layers, never on each other.
Enforcing the boundary
- SPM’s dependency graph — undeclared dependency edges mean compile errors.
- Access control discipline.
- Dependency graph linting tools.
Communication between feature modules without direct dependencies
- Protocol-based interfaces owned by a shared module.
- A routing/deep-link layer at the composition root.
- Event/notification-based decoupling.
The composition root
The App target — depends on every feature module, wires up root navigation, performs DI, kept deliberately thin.
Practical granularity judgment
Modularize along boundaries that correspond to actual team ownership and actual independent release/iteration cadence.
One-liner if pressed: “At scale, I’d modularize along both axes — shared horizontal layers like Core, Networking, and DesignSystem that any feature can depend on, and vertical feature modules that depend downward on those layers but never on each other directly — enforced structurally through Swift Package Manager’s dependency graph rather than just convention, with a thin composition root at the top that’s the only place allowed to know about every feature at once.”