VIPER is a lightweight software architecture framework for Swift.
Clean Architecture is a software architecture pattern devised by Robert C. Martin in 2012 that promotes the SOLID principles of software design. The concept of VIPER is an iOS architecture pattern inspired by the Clean Architecture, originally coined by developers of Mutual Mobile and popularised by their objc.io article. This framework is a Swift implementation of the aforementioned architecture principles that enables you to build VIPER apps for iOS, macOS and tvOS.
In true Swift fashion, VIPER is only available as a Swift Package.
Add the following to your
.package(url: "firstname.lastname@example.org:thomverbeek/VIPER.git", from: "0.5.1"),
In your project or workspace, choose
File ▸ Swift Packages ▸ Add Package Dependency… to add
https://github.com/thomverbeek/VIPER as a package dependency.
This package comes bundled with
viper-tools, a command line utility.
cdinto this Swift package, and run the following command:
$ swift run viper-tools
generatesubcommand to generate a new VIPER module. For example, to generate a module called "MyModule" for macOS on the Desktop:
$ swift run viper-tools generate MyModule ~/Desktop/ --os macOS --verbose
--exclude-directoryflag to generate files without a directory. This is very useful when grouping your VIPER modules into Swift Packages in your project.
VIPER divides application logic into distinct components of responsibility:
View: UI logic, including any user interaction;
Interactor: business logic, akin to application use cases;
Presenter: presentation logic, which maps business logic to view logic;
Entity: entity logic, maintained by repositories & services;
Router: navigation logic, which lives in the same realm as the view.
Conceptually, these five components form a collective
Module, synonymous with a single screen in your iOS application. The lifecycle of each module is visually represented by the
View, which indirectly holds reference to all components. These components communicate with one another in an orchestrated order, and once the
View dismisses, the lifecycle ends. The resulting code is clear, testable, modular and scalable with large teams.
The VIPER manifesto isn't without its flaws:
Router's role wasn't clearly defined, making it difficult to implement. VIPER intended to solve the Assembler Problem by enabling the
Routerto assemble VIPER modules. But this arrangement jeopardises the Single Responsibility Principle as it already takes responsibility for navigation. And speaking of navigation, the
Router's task to pass information between VIPER modules was also left in the dark.
Entitycomponent was likely a catch-all term for "anything else" in the VIPER backronym. It lacked a clear definition, hinting at an entity layer exclusively for the
Interactorto interact with. An ethereal interpretation sees simple entities forming the basis of all message passing between the various layers of the VIPER module. In practice, it's likely that
Entityencompasses any repositories or services an
Interactormay engage with. Without clarity on where these repositories or services come from, the VIPER definition likely needs to include dependency injection.
There are numerous implementations out in the wild that try to meet these requirements and then some, but they leave a bit more to be desired. VIPER can be difficult to grasp and fully implement as a framework, and the Swift language throws even more hurdles in the mix due to its linguistic quirks and type-safe limitation. Most frameworks out there simply attempt to translate the original Objective-C sample code to Swift, forcing the developer to wire up components manually and force-cast between types. These frustrations ultimately led to the development of this framework to bring VIPER to the masses.
This framework leverages a combination of generics, static scopes and functional reactive programming principles to distill VIPER down to a single file of under 300 lines of code, including comments. Check out
Routerand grants it to the
Entitiesto define the dependencies of an
Builderto provide dependency injection to the
Interactoras the holder of state, and uses a PresenterModel to communicate between the business logic and presentation logic layers. Presenters use the PresenterModel to consult their state and update their ViewModel without exposing the Entity layer. This setup embraces modern practices like Reactive programming using Combine.
All this results in a VIPER architecture implementation that's simple and sophisticated. For that reason, it's simply called "VIPER".
It's a uni-directional viper, as emblazoned on the logo.
|Last commit: Yesterday|
This release drastically alters VIPER from a unidirectional to bidirectional implementation:
AnyObjectrequirement forces all VIPER objects to be implemented as classes. This allows for some Objective-C runtime magic to abstract message passing mechanics.
Combineis removed as a dependency, which lowers the minimum OS requirements. Message passing is handled under the hood via simple closures. The need for subscriptions is removed in favour of associated objects and function variable scopes.
Combinepublishers for message passing.