ViperBase es una implementación de la arquitectura Viper para usar en la plataforma iOS.
Este proyecto tiene como objetivo facilitar el uso y la adopción de Viper , facilitando todas las configuraciones necesarias para trabajar con esta arquitectura e tratar de reducir lo más posible el trabajo manual surgido debido a su característica.
Cocoapods es un gerente de dependencia para proyectos de cacao. Para integrar ViperBase en su proyecto XCode, especifíquelo en su Podfile :
pod 'viper-base' , '~> 2.1' La experiencia más mala que pueda tener al usar Viper es crear, por sí mismo , todos los archivos, clases y protocolos que necesitan esta arquitectura. Esto afecta mucho a la productividad, lo que hace que una tarea simple lleva mucho tiempo.
Xcode nos permite crear plantillas personalizadas. Con este recurso disponible, decidimos crear una plantilla especialmente para ViperBase . Usando esta plantilla, ya no habrá trabajo manual.
Para instalar y usar nuestra plantilla, consulte este tutorial.
Un módulo representa una pantalla de la aplicación.
Esta implementación de Viper tiene algunas particularidades:
Algunos enfoques consideran la tarea de creación del módulo como una responsabilidad del enrutador . Pero esto viola el principio de responsabilidad única , ya que ya es responsable de la navegación entre módulos.
Para resolver este problema, se creó Builder . Está a cargo de crear todos los componentes del módulo y hacer las conexiones respectivas.
De esta manera, las entidades se pueden usar en uno o más módulos, ya que son estructuras simples sin lógica comercial.
Arquitectura de iOS, la navegación se realiza desde un UIViewController a otro UIViewController . Debido a esto, el enrutador tiene que ser dueño de la Refecencia para la vista del módulo actual, solo para fines de navegación , y recibir la vista del módulo de destino, desde su constructor.
Los contratos definen cómo se realizará la comunicación entre las capas. Un módulo consta de 5 contratos:
La clase View se ajusta a este protocolo. Define la comunicación del presentador a la vista
// MARK: - View Contract
protocol MyModuleViewProtocol : class {
} La clase presentadora se ajusta a este protocolo. Define la comunicación de la vista al presentador
// MARK: - View Output Contract
protocol MyModuleViewOutputProtocol : class {
} La clase interactora se ajusta a este protocolo. Define la comunicación del presentador al interactor
// MARK: - Interactor Contract
protocol MyModuleInteractorProtocol : class {
} La clase presentadora se ajusta a este protocolo. Define la comunicación de interactor a presentador
// MARK: - Interactor Output Contract
protocol MyModuleInteractorOutputProtocol : class {
} La clase de enrutador se ajusta a este protocolo. Define la comunicación del presentador al enrutador
// MARK: - Router Contract
protocol MyModuleRouterProtocol : class {
} final class MyModuleView : UIViewController , VIPERView {
var presenter : MyModuleViewOutputProtocol !
}
// MARK: - MyModuleViewProtocol
extension MyModuleView : MyModuleViewProtocol {
}Solo necesita implementar los métodos definidos en el contrato de vista .
final class MyModulePresenter : VIPERPresenter {
weak var view : MyModuleViewProtocol !
var interactor : MyModuleInteractorProtocol !
var router : MyModuleRouterProtocol !
}
// MARK: - MyModuleViewOutputProtocol
extension MyModulePresenter : MyModuleViewOutputProtocol {
}
// MARK: - MyModuleInteractorOutputProtocol
extension MyModulePresenter : MyModuleInteractorOutputProtocol {
}Solo necesita implementar los métodos definidos en el contrato de salida View y el contrato de salida del interactor .
final class MyModuleInteractor : VIPERInteractor {
weak var presenter : MyModuleInteractorOutputProtocol !
}
// MARK: - MyModuleInteractorProtocol
extension MyModuleInteractor : MyModuleInteractorProtocol {
}Solo necesita implementar los métodos definidos en el contrato del interactor .
final class MyModuleRouter : VIPERRouter {
weak var viewController : UIViewController !
}
// MARK: - MyModuleRouterProtocol
extension MyModuleRouter : MyModuleRouterProtocol {
}Solo necesita implementar los métodos definidos en el contrato del enrutador .
En el enrutador, la navegación se puede hacer de dos maneras: presentar el módulo modalmente o empujar el módulo a una pila de navegación . Para realizar la navegación, use los métodos a continuación:
- PresentModule (WithView: Inbedin: Animado: finalización :)
Este método presenta el siguiente módulo modalmente. Verifique los detalles de los parámetros a continuación:
withView : Ver del módulo para navegar.embedIn : .navigationController o .none . El valor predeterminado es .noneanimated : si realiza o no la animación de la transición. El valor predeterminado es truecompletion : Handler llamado cuando termina la transición. El valor predeterminado es nil- PushModule (WithView: Inbedin: animado :)
Este método empuja el siguiente módulo a la pila de navegación . Solo funciona si el módulo actual se incrusta en un controlador de navegación o es parte de una pila de navegación.
withView : Ver del módulo para navegar.embedIn : .navigationController o .none . El valor predeterminado es .noneanimated : si realiza o no la animación de la transición. El valor predeterminado es true En la clase Builder, especifica las clases respectivas para las capas View , Presenter , Interactor y router para el módulo.
final class MyModuleBuilder : VIPERBuilder < MyModuleView , MyModulePresenter , MyModuleInteractor , MyModuleRouter > {
override class var defaultViewUIType : VIPERViewUIType {
return . storyboard ( name : " MyModuleView " , bundle : nil )
}
}
// MARK: - Builder custom methods
extension MyModuleBuilder {
} También define la forma en que se cargará la UI View , a través de la propiedad defaultViewUIType . Hay 3 valores posibles:
. storyboard ( name : " MyModuleView " , bundle : nil ) . nib ( name : " MyModuleView " , bundle : nil ) . noneLos 4 métodos a continuación se pueden usar para construir un módulo. Además, puede crear métodos de compilación personalizados , de acuerdo con las necesidades del módulo.
- construir() :
Crea el módulo y devuelve una estructura de VIPERModule que contiene la view y las referencias presenter . Puede usar la referencia del presentador para pasar datos entre los módulos .
// MARK: - MyModuleRouterProtocol
extension MyModuleRouter : MyModuleRouterProtocol {
func goToNextModule ( ) {
let module = NextModuleBuilder . build ( )
pushModule ( withView : module . view )
}
}- Build (ViewuityPe :) :
Este método funciona como el método anterior, pero le permite especificar el tipo de interfaz de usuario durante la llamada del método. Este método es conveniente cuando está utilizando typealias para definir la configuración del constructor de módulos.
typealias MyModuleBuilder = VIPERBuilder < MyModuleView , MyModulePresenter , MyModuleInteractor , MyModuleRouter >
MyModuleBuilder . build ( viewUIType : . storyboard ( name : " MyModuleView " , bundle : nil ) )También puede usar este método si intenta realizar pruebas unitarias alrededor de la comunicación del módulo, burlándose de una o más clases de capa , de acuerdo con las necesidades de la prueba.
import XCTest
class ProjectTests : XCTestCase {
//...
func testModuleCommunication ( ) {
typealias MockModuleBuilder = VIPERBuilder < ModuleMockView , ModuleOriginalPresenter , ModuleOriginalInteractor , ModuleOriginalRouter >
let module = MockModuleBuilder . build ( )
//...
}
} - [Depreciado] buildAndAdattActtowindow ()
Este es un método de compilación especial, generalmente utilizado para iniciar el módulo inicial de la aplicación, llamado en la clase AppDelegate :
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = InitialModuleBuilder.buildAndAttachToWindow()
return true
}
}
[Actualizado]: Ahora debe construir el módulo primero , utilizando build() o build(viewUIType:) Métodos, luego llame attachToWindow() o attachToWindow(withNavigationController:) Nuevos métodos:
window = InitialModuleBuilder . build ( ) . attachToWindow ( ) window = InitialModuleBuilder . build ( ) . attachToWindow ( withNavigationController : true )[IMPORTANTE]: está planeado para eliminar el método desaprobado en la próxima versión principal (v3.0)
- [Depreciado] BuildAndAdattActonAVigationController (TabBaritem :)
Este método crea el módulo, adjunte a un controlador de navegación y devuelve la referencia del controlador de navegación . Si tiene la intención de usar el módulo dentro de un controlador de barra de pestaña , puede usar el parámetro tabBarItem para configurar el elemento de la barra de pestañas para este módulo.
let tabBarController = UITabBarController()
let bookmarksItem = UITabBarItem(tabBarSystemItem: .bookmarks, tag: 0)
let contactsItem = UITabBarItem(tabBarSystemItem: .contacts, tag: 1)
let downloadsItem = UITabBarItem(tabBarSystemItem: .downloads, tag: 2)
tabBarController.viewControllers = [
BookmarksBuilder.buildAndAttachToNavigationController(tabBarItem: bookmarksItem),
ContactsBuilder.buildAndAttachToNavigationController(tabBarItem: contactsItem),
DownloadsBuilder.buildAndAttachToNavigationController(tabBarItem: downloadsItem)
]
[Actualizado]: Ahora primero debe construir el módulo , utilizando build() o build(viewUIType:) Métodos, luego llame al nuevo método attachToNavigationController() :
window = MyModuleBuilder . build ( ) . attachToNavigationController ( )[IMPORTANTE]: está planeado para eliminar el método desaprobado en la próxima versión principal (v3.0)
Si tiene un módulo específico que espera recibir algunos datos , es conveniente crear un método de compilación personalizado para ese módulo. De esa manera, el constructor estará a cargo de pasar estos datos al presenter .
Para crear métodos de compilación personalizados:
build() ;presenter , según la implementación;view . // MARK: - Builder custom methods
extension NextModuleBuilder {
static func build ( someData : Any , anotherData : Any ) -> MyModuleView {
let module = build ( )
module . presenter . someData = someData
module . presenter . anotherData = anotherData
return module . view
}
}Los enrutadores pueden llamar a este constructor así:
let view = NextModuleBuilder . build ( someData : " Example of data " , anotherData : " Another example of data " ) pushModule ( withView : view ) presentModule ( withView : view ) ViperBase se libera bajo la licencia MIT.