VISPER-Swift
VISPER is a component based library, which helps you to develop modular apps based on the VIPER Pattern. VISPER contains of several components to create a flexibel architecture for your iOS-app. It contains of 3 main components.
The most general is the App protocol, which helps you to compose your app of seperated modules called Features. Each Feature creates and configures a distinct part of application. VISPER provides you with the Wireframe , since the navigation between view controllers is an important aspect of your application, which should be modelt explicitly without coupling your different UIViewControllers. The Wireframe a powerful router component, which defines a defined workflow for creating, presenting and navigation between ViewControllers without requiering a view controller to know about the implementation of the next presented view controller. An other important concern when creating a new app is state management. In an VISPER-App this is typically done by a Redux Object which lives in the interactor layer and provides a complete Redux-Architecture to manage your appstate and it’s transitions.
Getting started
App
The core component of your VISPER application is an instance of the App protocol, which allows you to configure your application by a Feature which represents a distinct functionality of your app and configures all VISPER components used by it.
The definition of the App protocol is quite simple:
public protocol App {
/// Add a feature to your application
///
/// - Parameter feature: your feature
/// - Throws: throws errors thrown by your feature observers
///
/// - note: A Feature is an empty protocol representing a distinct funtionality of your application.
/// It will be provided to all FeatureObservers after addition to configure and connect it to
/// your application and your remaining features. Have look at LogicFeature and LogicFeatureObserver for an example.
func add(feature: Feature) throws
/// Add an observer to configure your application after adding a feature.
/// Have look at LogicFeature and LogicFeatureObserver for an example.
///
/// - Parameter featureObserver: an object observing feature addition
func add(featureObserver: FeatureObserver)
}
Features and FeatureObserver
You can basicly add some FeatureObservers and Features to an App.
A FeatureObserver will be called whenever a Feature is added and is responsible for configuring your VISPER components to provide the functionality implemented by your Feature.
Many VISPER Components implement their own subtypes of App, Feature and FeatureObserver.
- VISPER-Wireframe provides you with:
- WireframeApp
- ViewFeature
- PresenterFeature
- WireframeFeatureObserver
- ViewFeatureObserver
VISPER-Redux provides you with:
VISPER-Swift provides you with a VISPERApp which combines all characteristics of a WireframeApp and a ReduxApp and is used in the most Apps build with the VISPER-Framework.
Wireframe
The Wireframe manages the lifecycle of UIViewController in an VISPER-Application. It is used to create controllers and to route from one controller to an other. It seperates the ViewController presentation and creation logic from the UIViewController itself.
The VISPER-Wireframe component contains an implementation of the Wireframe-Protocol.
You can use the DefaultWireframeAppFactory to create a WireframeApp with a default configuration:
let navigationController = UINavigationController()
let factory = DefaultWireframeAppFactory()
let wireframeApp = factory.makeApp()
wireframeApp.navigateOn( navigationController)
if you want to create a Wireframe without creating a WireframeApp use the WireframeFactory.
let factory = WireframeFactory()
let wireframe = factory.makeWireframe()
Now create a ViewFeature which provides a ViewController and some RoutingOptions, to define how the controller will be presented, to your wireframe.
class ExampleViewFeature: ViewFeature {
var routePattern: String = "/exampleView"
var priority: Int = 0
//controller will be pushed on current active navigation controller
func makeOption(routeResult: RouteResult) -> RoutingOption {
return DefaultRoutingOptionPush()
}
func makeController(routeResult: RouteResult) throws -> UIViewController {
let controller = UIViewController()
return controller
}
}
Add it to your WireframeApp
let feature = ExampleFeature()
wireframeApp.add(feature: feature)
or to your Wireframe
let feature = ExampleFeature()
wireframe.add(controllerProvider: feature, priority: feature.priority)
wireframe.add(optionProvider: feature, priority: feature.priority)
try wireframe.add(routePattern: feature.routePattern)
You can now route to the controller provided by the ExampleFeature:
try wireframe.route(url: URL(string: "/exampleView")!)
Here ist a full example using VISPER with a wireframe
VISPER-Redux
VISPER-Redux is an implementation of the redux-architecture in swift.
It provides you with an app architecture to tackle the problem of distributed app state and state changes. It is the implementaion of the interactor layer in many Apps based on the VISPER Framework.
If you want to learn more about redux, have a look at the following tutorials and documentations:
A comprehensive introduction about VISPER-Redux can be found here.
State
VISPER-Redux stores the complete state of your app in a central struct to create a transparent representation of the current state of your different app components.
A typical composite app state for an app to manage your todos in the next week might look like that:
struct AppState {
var userState: UserState
var todoListState: TodoListState
var imprintState: ImprintState
}
with some composite sub states:
struct UserState {
var userName: String
var isAuthenticated: Bool
}
struct TodoListState {
var todos: [Todo]
}
struct ImprintState {
var text: String
var didAlreadyRead: Bool
}
AppReducer
Each store has a special reducer with the following definition:
public typealias AppReducer<State> = (_ ReducerProvider: ReducerProvider,_ action: Action, _ state: State) -> State
It is used as a single entrypoint to the store. It is called whenever a action is dispatched, to resolve a new state. Since our state is generic it is necessary to delegate the creation of each state property to the reducerProvider parameter.
An AppReducer for the previously defined AppState should look like that:
let appReducer = { (reducerProvider: ReducerProvider, action: Action, currentState: AppState) -> AppState in
let state = AppState(
userState: reducerProvider.reduce(action,currentState.userState),
todoListState: reducerProvider.reduce(action,currentState.todoListState),
imprintState : reducerProvider.reduce(action,currentState.imprintState)
)
return reducerProvider.reduce(action,state)
}
ReduxApp
Createing an redux app is simple
let appState: AppState = AppState( ... create your state here ...)
let factory = ReduxAppFactory()
let app: ReduxApp<AppState> = factory.makeApplication(initialState: appState, appReducer: appReducer)
Changing state
The current state in an app using VISPER-Redux is stored in a central Store instance, which lives in a convinience wrapper object of type Redux. State change can only be achieved by dispatching an action (a simple message object) at the ActionDispatcher, and creating a modified new state in a Reducer (A reduce-function or an instance of type FunctionalReducer,ActionReducerType or AsyncActionReducerType).
A reduce-function has the following form (where ActionType and StateType are generic types of type Action and Any):
(_ provider: ReducerProvider,_ action: ActionType, _ state: StateType) -> StateType
the reduce-function/reducer will be applied to all actions of type ActionType and to all states of type StateType. A reducer can be added to your redux architecture by adding it to the reducer container.
// add a reduce function
app.redux.reducerContainer.addReduceFunction(...)
// add a action reducer instance
app.redux.reducerContainer.addReducer(...)
An action is just an simple object conforming to the empty protocol Action, for example:
struct SetUsernameAction: Action {
var newUsername: String
}
let action = SetUsernameAction(newUsername: "Max Mustermann")
app.redux.actionDispatcher.dispatch(action)
Reducer
Reducers specify how the application’s state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t describe how the application’s state changes.
A reducer in VISPER swift could be a reduce-function, or an instance of type FunctionalReducer,ActionReducerType
or AsyncActionReducerType.
ReduceFuntion
A reduce funtion is just a simple function getting a provider, an action and an state, and returning a new state of the same type.
let reduceFunction = { (provider: ReducerProvider, action: SetUsernameAction, state: UserState) -> UserState in
return UserState(userName: action.newUsername,
isAuthenticated: state.isAuthenticated)
}
reducerContainer.addReduceFunction(reduceFunction:reduceFunction)
FunctionalReducer
A functional reducer is quite similar, just a reducer taking a reduce function and using it to reduce a state.
let functionalReducer = FunctionalReducer(reduceFunction: reduceFunction)
reducerContainer.addReducer(reducer:functionalReducer)
ActionReducerType
An action type reducer is a class of type ActionReducerType which contains a reduce function for a specific action and state type.
struct SetUsernameReducer: ActionReducerType {
typealias ReducerStateType = UserState
typealias ReducerActionType = SetUsernameAction
func reduce(provider: ReducerProvider,
action: SetUsernameAction,
state: UserState) -> UserState {
return UserState(userName: action.newUsername,
isAuthenticated: state.isAuthenticated)
}
}
let reducer = SetUsernameReducer()
reducerContainer.addReducer(reducer:reducer)
AsyncActionReducerType
An async reducer is an reducer of AsyncActionReducerType which does not return a new state, but calls a completion with a new state.
struct SetUsernameReducer: AsyncActionReducer {
typealias ReducerStateType = UserState
typealias ReducerActionType = SetUsernameAction
let currentState: ObserveableProperty<UserState>
func reduce(provider: ReducerProvider,
action: SetUsernameAction,
completion: @escaping (_ newState: UserState) -> Void) {
let newState = UserState(userName: action.newUsername,
isAuthenticated: self.currentState.value.isAuthenticated)
completion(newState)
}
}
let reducer = SetUsernameReducer(currentState: app.state.map{ $0.userState })
reducerContainer.addReducer(reducer:reducer)
LogicFeature
You can use a LogicFeature to add some reducers to your app.
import VISPER_Redux
class ExampleLogicFeature: LogicFeature {
func injectReducers(container: ReducerContainer) {
let reducer = SetUsernameReducer()
container.addReducer(reducer: incrementReducer)
}
}
let logicFeature = ExampleLogicFeature()
app.add(feature: let logicFeature)
Observing state change
Observing state change ist simple. Just observe the state of your app:
//get your reference bag to retain subscription
let referenceBag: SubscriptionReferenceBag = self.referenceBag
//subscribe to the state
let subscription = app.state.subscribe { appState in
print("New username is:\(appState.userState.userName)")
}
//retain subscription in your reference bag
referenceBag.addReference(reference: subscription)
VISPER-Redux contains a ObservableProperty
Complete list of all available VISPER-Components
- VISPER - a convenience import wrapper to include all VISPER Components with one import. It contains some deprecated components for backwards compatibility to previous VISPER Versions.
- VISPER-Swift - All swift components of the VISPER-Framework, and a convenience import wrapper for all their dependencies.
- VISPER-Objc - A wrapper around the core VISPER classes to use them in an objc codebase.
- VISPER-Core - Some common core protocols used to communicate between the different components of your feature. This pod should be used if you want to include VISPER Components into your own projects and components. It’s protocols are implemented in the other VISPER component pods.
- VISPER-Wireframe - The component containing the implementation of the wireframe layer in a VIPER-Application, it manages the presentation and the lifecycle of your ViewControllers.
- VISPER-Presenter(swift / objc) - The component containing the implementation of the presentation layer in a VIPER-Application. It contains some presenter classes to seperate your application logic, from your view logic.
- VISPER-Redux - A component containing the implementation of an redux architecture used in many VISPER-Application to represent the interactor layer in a viper application.
- VISPER-Reactive - A simple implementation of reactive properties to allow the use of a reactive redux architecture in a VISPER-Application. It can be updated by the subspec VISPER-Rective/RxSwift to use the RxSwift framework.
- VISPER-Sourcery - A component supporting you to create a VISPER application by creating some nessecary boilerplate code for you.
- VISPER-UIViewController (swift) / (objc) - A component extending UIViewControllers to notify a presenter about it’s lifecycle (viewDidLoad, etc.)
- VISPER-Entity - A component modeling the entity layer if you do not use your custom layer in your VISPER-Application.