
(
Ilustração )
Cada equipe, mais cedo ou mais tarde, começa a pensar em introduzir suas próprias abordagens arquitetônicas, e muitas cópias foram quebradas. Então, na
Umbrella IT, sempre quisemos trabalhar com ferramentas flexíveis para que a formação da arquitetura não fosse algo doloroso, e os problemas de navegação, arquivos simulados, isolamento e teste deixaram de ser algo assustador, algo que mais cedo ou mais tarde pairando sobre um projeto coberto de vegetação. Felizmente, não estamos falando de uma nova arquitetura "exclusiva" com uma abreviação pretensiosa. Devo admitir que as arquiteturas atualmente populares (MVP, MVVM, VIPER, Clean-swift) estão lidando com suas tarefas, e apenas a escolha errada e o uso errado desta ou daquela abordagem podem causar dificuldades. No entanto, dentro da estrutura da arquitetura adotada, vários padrões podem ser usados, o que permitirá alcançar esses indicadores quase míticos: flexibilidade, isolamento, testabilidade, reutilização.
Obviamente, as aplicações são diferentes. Se um projeto contiver apenas algumas telas conectadas em série, não haverá necessidade específica de interações complexas entre os módulos. É possível fazer com as segu-conexões usuais, temperando tudo isso com o bom e velho MVC / MVP. E, embora o esnobismo arquitetônico, mais cedo ou mais tarde, derrote todos os desenvolvedores, ainda assim a implementação deve ser proporcional aos objetivos e à complexidade do projeto. E assim, se o projeto envolver uma estrutura de tela complexa e vários estados (autorização, modo Convidado, offline, funções para usuários etc.), uma abordagem simplificada da arquitetura certamente será um truque: muitas dependências, uma transferência de dados não óbvia e cara entre telas e estados, problemas com a navegação e, o mais importante - tudo isso não terá flexibilidade e reutilização, as soluções serão fortemente fundidas no projeto e a tela A sempre abrirá a tela B. As tentativas de fazer alterações levarão a refinadores dolorosos ngam durante o qual é tão fácil cometer erros e quebrar o que costumava funcionar. No exemplo abaixo, descreveremos uma maneira flexível de organizar a operação de um aplicativo que possui dois estados: o usuário não está autorizado e deve ser direcionado para a tela de autorização, o usuário está autorizado e uma certa tela principal deve ser aberta.
1. Implementação dos principais protocolos
Primeiro, precisamos implementar a base. Tudo começa com os protocolos coordenáveis, apresentáveis e roteáveis:
protocol Coordinatable: class { func start() } protocol Presentable { var toPresent: UIViewController? { get } } extension UIViewController: Presentable { var toPresent: UIViewController? { return self } func showAlert(title: String, message: String? = nil) { UIAlertController.showAlert(title: title, message: message, inViewController: self, actionBlock: nil) } }
Neste exemplo, showAlert é apenas um método conveniente para chamar uma notificação, que está na extensão UIViewController. protocol Routable: Presentable { func present(_ module: Presentable?) func present(_ module: Presentable?, animated: Bool) func push(_ module: Presentable?) func push(_ module: Presentable?, animated: Bool) func push(_ module: Presentable?, animated: Bool, completion: CompletionBlock?) func popModule() func popModule(animated: Bool) func dismissModule() func dismissModule(animated: Bool, completion: CompletionBlock?) func setRootModule(_ module: Presentable?) func setRootModule(_ module: Presentable?, hideBar: Bool) func popToRootModule(animated: Bool) }
2. Crie um coordenador
De tempos em tempos, é necessário alterar as telas dos aplicativos, o que significa que será necessário implementar a camada testada sem downcast e sem violar os princípios do SOLID.
Prosseguimos com a implementação da camada de coordenadas:

Após o início do aplicativo, o método AppCoordinator deve ser chamado, o que determina qual fluxo deve ser iniciado. Por exemplo, se o usuário estiver registrado, você deverá executar o aplicativo de fluxo e, caso contrário, a autorização de fluxo. Nesse caso, MainCoordinator e AuthorizationCoordinator são necessários. Vamos descrever o coordenador para autorização, todas as outras telas podem ser criadas de maneira semelhante.
Primeiro, você precisa adicionar saída ao coordenador para que ele possa ter uma conexão com um coordenador superior (AppCoordinator):
protocol AuthorizationCoordinatorOutput: class { var finishFlow: CompletionBlock? { get set } } final class AuthorizationCoordinator: BaseCoordinator, AuthorizationCoordinatorOutput { var finishFlow: CompletionBlock? fileprivate let factory: AuthorizationFactoryProtocol fileprivate let router : Routable init(router: Routable, factory: AuthorizationFactoryProtocol) { self.router = router self.factory = factory } }

Como mostrado acima, temos um coordenador de autorização com um roteador e uma fábrica de módulos. Mas quem e quando chama o método start ()?
Aqui precisamos implementar o AppCoordinator.
final class AppCoordinator: BaseCoordinator { fileprivate let factory: CoordinatorFactoryProtocol fileprivate let router : Routable fileprivate let gateway = Gateway() init(router: Routable, factory: CoordinatorFactory) { self.router = router self.factory = factory } } // MARK:- Coordinatable extension AppCoordinator: Coordinatable { func start() { self.gateway.getState { [unowned self] (state) in switch state { case .authorization: self.performAuthorizationFlow() case .main: self.performMainFlow() } } } } // MARK:- Private methods func performAuthorizationFlow() { let coordinator = factory.makeAuthorizationCoordinator(with: router) coordinator.finishFlow = { [weak self, weak coordinator] in guard let `self` = self, let `coordinator` = coordinator else { return } self.removeDependency(coordinator) self.start() } addDependency(coordinator) coordinator.start() } func performMainFlow() { // MARK:- main flow logic }
No exemplo, você pode ver que o AppCoordinator possui um roteador, uma fábrica de coordenadores e o estado do ponto de entrada do AppCoordinator, cuja função é determinar o início do fluxo do aplicativo.
final class CoordinatorFactory { fileprivate let modulesFactory = ModulesFactory() } extension CoordinatorFactory: CoordinatorFactoryProtocol { func makeAuthorizationCoordinator(with router: Routable) -> Coordinatable & AuthorizationCoordinatorOutput { return AuthorizationCoordinator(router: router, factory: modulesFactory) } }
3. Implementação dos coordenadores da fábrica
Cada um dos coordenadores é inicializado com um roteador e uma fábrica de módulos. Além disso, cada um dos coordenadores deve herdar do coordenador da base:
class BaseCoordinator { var childCoordinators: [Coordinatable] = [] // Add only unique object func addDependency(_ coordinator: Coordinatable) { for element in childCoordinators { if element === coordinator { return } } childCoordinators.append(coordinator) } func removeDependency(_ coordinator: Coordinatable?) { guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return } for (index, element) in childCoordinators.enumerated() { if element === coordinator { childCoordinators.remove(at: index) break } } } }
BaseCoordinator - uma classe que contém uma matriz de coordenadores filhos e dois métodos: Excluir e adicionar dependência do coordenador.
4. Configurando AppDelegate
Agora vamos ver como é o
UIApplicationMain: @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var rootController: UINavigationController { window?.rootViewController = UINavigationController() window?.rootViewController?.view.backgroundColor = .white return window?.rootViewController as! UINavigationController } fileprivate lazy var coordinator: Coordinatable = self.makeCoordinator() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { coordinator.start() return true } }
Assim que o método delegate didFinishLaunchingWithOptions for chamado, o método start () do AppCoordinator será chamado, o que determinará a lógica adicional do aplicativo.
5. Criando um módulo de tela
Para demonstrar o que acontece a seguir, vamos voltar ao AuthorizationCoordinator e implementar o método performFlow ().
Primeiro, precisamos implementar a interface AuthorizationFactoryProtocol na classe ModulesFactory:
final class ModulesFactory {} // MARK:- AuthorizationFactoryProtocol extension ModulesFactory: AuthorizationFactoryProtocol { func makeEnterView() -> EnterViewProtocol { let view: EnterViewController = EnterViewController.controllerFromStoryboard(.authorization) EnterAssembly.assembly(with: view) return view
Chamando qualquer método em uma fábrica de módulos, como regra, queremos dizer inicializar o ViewController a partir do storyboard e, em seguida, vincular todos os componentes necessários desse módulo dentro de uma arquitetura específica (MVP, MVVM, CleanSwift).
Após as preparações necessárias, podemos implementar o método performFlow () do AuthorizationCoordinator.
A tela inicial dentro deste coordenador é EnterView.
No método performFlow (), usando a fábrica de módulos, é chamada a criação de um módulo pronto para o coordenador fornecido, a lógica de processamento dos fechamentos que nosso controlador de exibição chama uma vez ou outra é implementada; esse módulo é configurado pelo roteador como raiz na pilha de telas de navegação:
private extension AuthorizationCoordinator { func performFlow() { let enterView = factory.makeEnterView() finishFlow = enterView.onCompleteAuthorization enterView.output?.onAlert = { [unowned self] (message: String) in self.router.toPresent?.showAlert(message: message) } router.setRootModule(enterView) } }

Apesar da aparente complexidade em alguns lugares, esse padrão é ideal para trabalhar com arquivos simulados, permite isolar completamente os módulos um do outro e também nos abstrai do UIKit, que é adequado para uma cobertura completa do teste. Ao mesmo tempo, o Coordinator não impõe requisitos rígidos à arquitetura do aplicativo e é apenas uma adição conveniente, estruturando a navegação, as dependências e os fluxos de dados entre os módulos.
Link para o github , que contém uma demonstração baseada na arquitetura Clean e um conveniente Xcode Template para criar as camadas arquitetônicas necessárias.