Pentimento

A simple, unobtrusive, unidirectional data flow architecture.

Why we built it.

Apple's Model View Controller (MVC) architecture sits at the heart of every iOS app. Apple's MVC-based UIKit framework provides windows, views, and all the elements you need to implement your app's interface. It doesn't make sense to build an app without it. However, UIKit employs and encourages harmful patterns that lead to some common problems:

  • Large classes with many dependencies and responsibilities
  • Tightly coupled components that share application state via singletons
  • Unpredictable state mutation

These qualities make apps difficult to understand, test, and modify.

Pentimento is a collection of principles and patterns that we use to avoid these pitfalls while embracing the best parts of UIKit.

How it works.

Pentimento consists of four main principles:

  • Maximize Testability
  • Encourage Predictable State Mutation
  • Use the Coordinator Pattern
  • Embrace UIKit

Maximize Testability

Maximizing testability isn't about achieving 100% unit test coverage. It's about creating components that are easy to understand, loosely coupled, reusable, and easy to change or remove over time. One way Pentimento achieves this is through dependency injection. Dependencies are clear and obvious, with injection occurring during object initialization. Dependencies can be mocked for testing, and components can be tested in isolation. Dependencies include UIKit singletons, allowing Pentimento apps to play well with UIKit, while maintaining maximum testability.

Encourage Predictable State Mutation

Pentimento emphasizes a unidirectional data flow, where so-called processors manage the application state. Only processors change the state, no one else. Views display the state as they receive it and never change it. This eliminates a class of bugs where it's unclear where a state change originated. In Pentimento, unlike in MVC, view controllers live squarely in the view layer and hence never change the application state.

Besides rendering the UI, the view layer also responds to user feedback. To handle feedback without touching the application state, Pentimento views dispatch simple actions conveying a request. Processors receive actions and respond appropriately, whether it's presenting a new screen, making a network request, or updating the current state. This strict separation of concerns allows each piece to be tested in isolation.

Use the Coordinator Pattern

Soroush Khanlou devised the Coordinator pattern to remove navigation responsibilities from view controllers. This greatly simplifies view controllers and removes dependencies between them.

With the Coordinator pattern, view controllers don't know anything about each other, how they were presented, or how to present other view controllers—a stark contrast to traditional MVC. Since they no longer need to manage each other, view controllers are simpler, substantially smaller, and focused solely on presenting state and listening for user interaction. Operating in isolation, they can then be tested in isolation.

Embrace UIKit

Pentimento strives to play well with UIKit. At the same time, it actively avoids UIKit pitfalls.

  • It embraces UIKit singletons but decouples components from them via protocolization and dependency injection.
  • It leaves the view hierarchy under UIKit control but decouples view controllers from one another via the Coordinator pattern.
  • It lets UIKit maintain internal state in objects like UICollectionView but uses a layer of indirection to maintain Pentimento's own unidirectional data flow.

How to use it.

Pentimento strives for a reasonably gentle learning curve. Because there's no framework, getting started with Pentimento largely comes down to understanding its patterns and conventions. The following diagram gives a simplified view of Pentimento's structure and flow.

Built by Livefront.

Livefront is a digital product consultancy. We're trusted by some of the world's most admired companies to drive strategy, design, and engineering for their core digital experiences. They partner with us to move faster, think bigger, and design products people love.

Learn more