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 a hundred lines of code. 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 exchanges the
Interactorin the assembly. This allows a uni-directional data flow from
View, more closely in line with the Clean Architecture.
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: 5 weeks ago|
This build simplifies the Combine sink-store mechanic to set up subscriptions for inter-component callbacks.