Scalable and Modular System Design for the simplest iOS App

Taha Bebek
4 min readDec 15, 2023

As an iOS engineer passionate about crafting efficient and maintainable applications, I want to share a case study that illustrates the power of modular architecture in app development, specifically using Swift and SwiftUI.

The App Overview

Our app is simple, featuring two main screens: the Feed Feature and the Detail View Feature.
Feed Feature: This is the first screen of our app, where users see a list of posts. Each post comprises a picture and a title.

Detail View Feature: Upon selecting a post from the Feed Feature, users are navigated to this screen. It displays the post’s detailed information, including a large image, title, and descriptive text.

Components

  1. FeedFeature: Manages the display of posts.
    — Dependencies: ApiClientProtocol, NavigationManagerProtocol, AnalyticsProtocol, Models
  2. DetailViewFeature: Shows detailed information about a selected post.
    — Dependencies: NavigationManagerProtocol, AnalyticsProtocol, Models
  3. DependencyManager: Provides necessary dependencies for features.
    — Dependencies: ApiClientImplementation, NetworkServiceImplementation, AnalyticsImplementation, URLSessionImplementation, Models
  4. NavigationManager: Creates features and handles navigation between screens.
    — Dependencies: DependencyManagerProtocol, FeedFeature, DetailFeature, Models
  5. ApiClient: Interfaces with the network to fetch post data.
    — Dependencies: NetworkServiceProtocol, Models
  6. NetworkService: Executes the network requests.
    Dependencies: URLSessionProtocol
  7. AnalyticsManager: Tracks user interactions and app events for analytics purposes.

Interaction Between Components

1- Dependency Injection: DependencyManager plays a crucial role in injecting dependencies into features. For instance, it supplies ApiClient, Analytics, and other necessary components to FeedFeature.

2- Data Flow: When a post is selected in FeedFeature, the NavigationManager is invoked to transition to DetailViewFeature, carrying along the necessary data (like post details).

3- Modularity: Each component (like FeedFeature, DetailViewFeature) operates independently but communicates with others via well-defined interfaces (like protocols). This setup allows for easy updates and maintenance of individual components.

The Power of Modular Architecture in iOS

Building Independently Compilable Features
The core of a well-structured iOS app lies in its modular architecture. By encapsulating each feature into its own module or framework, we achieve a level of independence that simplifies both development and testing. Tools like Swift Package Manager or Xcode Frameworks are essential here, allowing us to manage these modules effectively.

Imagine working on a complex app with multiple features — the ability to compile and run each feature independently is a massive time-saver. It also reduces the cognitive load, letting us focus on one aspect of the app at a time.

Protocols and Substitutability
Using protocols like ApiClientProtocol, NavigationManagerProtocol, and others ensures that components are not tightly coupled to specific implementations. This allows for easy substitution, such as using mock implementations for testing or switching out components without affecting the rest of the app.

Dependency Graph

Here is the dependency graph of this system. Dependencies are lazily created by the dependency manager bottom up and provided to the navigation manager as a scope. Creating the dependencies bottom-up is called dependency inversion. The arrow shows the import direction; for example, DependencyManager depends on URLSession. Every box in this graph is a separate module, located in an Xcode framework or a swift package.

Leveraging SwiftUI Previews with Mocked Dependencies

SwiftUI has been a game-changer, especially with its Preview feature. With a modular architecture, the ability to see real-time changes in the feature you are working on without the need to compile the entire app is nothing short of amazing.

Crafting Realistic Previews
By creating mock implementations of dependencies, we can inject controlled, predictable data into our SwiftUI views. This setup is perfect for developing features in isolation. It allows us to experiment with different UI states without the overhead of dealing with the actual backend or data sources.

A Practical Example

The FeedFeature requires data from an ApiClient and AnalyticsManager. Instead of relying on the actual implementations, we can use mock versions that provide predefined responses.

struct FeedFeaturePreview: PreviewProvider {
static var previews: some View {
FeedFeatureView(apiClient: MockApiClient(), …)
}
}

The Benefits Are Clear

The advantages of this methodology are numerous:

Rapid Iteration: You can tweak and refine UI components on the fly, significantly speeding up the development process.

Focused Testing: With mocked data, you can ensure your UI behaves exactly as expected under various conditions, without external dependencies.

Collaborative and Parallel Development: Different team members can work on separate features simultaneously without stepping on each other’s toes.

Modularity: You can develop, compile, test, and distribute modules independently.

Summary

Our app is a well-structured example of modular iOS architecture, emphasizing independence, testability, and maintainability. Each component has a specific role and communicates with others through defined interfaces and dependency injection, creating a robust and scalable design.

--

--