Como chegamos a uma nova abordagem para trabalhar com módulos no aplicativo RaiffeisenBank iOS.O problema
Nas aplicações do Raiffeisenbank, cada tela consiste em vários módulos que são tão independentes quanto possível um do outro. "Módulo" chamamos de componente visual que tem sua própria idéia. Ao projetar um aplicativo, é muito importante escrever lógica para que os módulos sejam independentes e possam ser facilmente adicionados ou removidos sem recorrer à refatoração.
Que dificuldades enfrentamos:
Destacando abstração sobre padrões arquiteturaisJá no primeiro estágio do desenvolvimento, ficou claro que não queríamos estar vinculados a um padrão arquitetural específico. O MVC é bom se você precisar exibir uma página com algumas informações. Ao mesmo tempo, a interação com o usuário é mínima ou nenhuma. Por exemplo: a página "sobre a empresa" ou "contrato de usuário". O VIPER é uma boa ferramenta para módulos complexos que têm sua própria lógica de trabalhar com serviços, roteamento e muito mais.
O problema da interação e encapsulamentoCada padrão arquitetural possui sua própria estrutura de construção e seus próprios protocolos, que impõem restrições ao trabalho com o módulo. Para abstrair o módulo, você precisa destacar as principais interfaces de interação de
entrada / saída .
Destacando a lógica de roteamentoUm módulo como unidade visual não deve e não pode saber onde e como é mostrado. Um e o mesmo módulo deve e pode ser implementado como uma unidade independente em qualquer tela ou como uma composição. A responsabilidade por isso não pode ser responsabilizada pelo próprio módulo.
Solução anterior: // Mau negócio
A primeira solução que escrevemos no Objective-C, e foi baseada no NSProxy. O problema de encapsulamento do padrão arquitetural foi resolvido por definição, que foi determinada pelas condições fornecidas, ou seja, a
entrada / saída do módulo, que tornou possível proxy de todas as chamadas do módulo para sua
entrada e receber mensagens através da
saída , se houver.
Foi um passo adiante, mas surgiram novas dificuldades:
- A interface do proxy não garantiu a implementação do protocolo de entrada ;
- A saída tinha que ser descrita, mesmo que não fosse necessária;
- Foi necessário adicionar a propriedade de saída à interface de entrada .
Além do
NSProxy, também implementamos o roteamento considerando a ideia do ViperMcFlurry: criamos uma categoria no
ViewController , que começou a crescer à medida que apareciam diferentes opções para exibir o módulo na tela. É claro que dividimos a categoria, mas ainda estava longe de ser uma boa solução.
Em geral ... a primeira panqueca é irregular, ficou claro que você precisa resolver o problema de maneira diferente.
Solução: // Final
Percebendo que não havia mais
nada com o
NSProxy , pegamos marcadores e fomos desenhar. Como resultado,
isolamos o protocolo
RFModule :
@objc protocol RFModule { var view: ViewController { get } var input: AnyObject? { get } var output: AnyObject? { get set } var transition: Transitioning { get set } }
Abandonamos propositalmente os tipos associados no nível do protocolo, e havia uma boa razão para isso: naquele momento, 90% do código estava no Objective-C. Interoperabilidade entre módulos ObjC ← → Swift não seria possível.
Para ainda usar genéricos e garantir o uso digitado de módulos, introduzimos a classe
Module que satisfaz o protocolo
RFModule :
final class Module<I: Any, O: Any>: RFModule { public typealias Input = I public typealias Output = O public var setOutput: ((O?) -> Void)?
Então, nós temos um módulo digitado. De fato, Swift usa a classe
Module e no Objective-C
RFModule . Além disso, acabou por ser uma ferramenta conveniente para tipos de trituração no local em que você precisa criar matrizes: por exemplo,
TabContainer .
Como o DI para criar o módulo está no
escopo do UserStory, e atribuir o valor da saída no local em que será usado não pode descrever um seter simples.
"SetOutput" é, em essência, uma
definição que, no estágio de atribuição da
saída, passará para a pessoa responsável, dependendo da lógica do módulo.
class SomeViewController: UIViewController, ModuleInput { weak var delegate: ModuleOutput } class Assembly { func someModule() -> Module<ModuleInput, ModuleOutput> { let view = SomeViewController() let module = Module<ModuleInput, ModuleOutput>(view: view, input: view) { [weak view] output in view?.delegate = output } return module } } ... let assembly: Assembly let module = assembly.someModule() module.output = self
A transição é um protocolo cujas implementações, como o nome indica, são responsáveis pela lógica de mostrar e ocultar o módulo.
protocol Transitioning { var destination: ViewController? { get }
Para exibição, é causado -
executar , ocultar -
reverter . Apesar do fato de haver
destino no protocolo e, a princípio, parece que deve haver
fonte . De fato, a
origem pode não ser e seu tipo nem sempre é o
ViewController . Por exemplo, se precisamos que o módulo seja aberto em uma nova janela, esta é
Window , e se precisamos
incorporar , precisamos AND parent:
ViewController AND container:
UIView .
class PresentTransition: Transitioning { weak var source: ViewController? weak var destination: ViewController? ... func perform(_ completion: (()->())?) { source.present(viewController: self.destinaton) } }
Assim, nos livramos da idéia de escrever extensões no
ViewController e descrevemos a lógica de como exibimos nossos módulos em vários objetos. Isso nos deu flexibilidade no roteamento, ou seja, agora podemos mostrar qualquer módulo de forma independente e complexa, e também variar entre como tudo é exibido na tela: na janela Presente, na navegação (pressione a navegação), incorporar, na cortina (capa) .
Isso é tudo?
Há mais uma coisa assustadora até agora. Para a oportunidade de escolher facilmente a maneira como o módulo é exibido e remover essa lógica, pagamos pela perda da capacidade de definir as propriedades da aparência. Por exemplo, se o mostrarmos em Navegação, precisaremos especificar qual cor deve ser a
barraTintColor ; ou, se mostrarmos o módulo na cortina, é necessário definir a cor do
manipulador .
Até agora, resolvemos esse problema com a aparência não digitada: Qualquer propriedade e Transição ao abrir o módulo levam ao tipo com o qual ele trabalha e, se for bem-sucedido, tira as propriedades necessárias.