Todos os anos, a plataforma iOS sofre muitas alterações. Além disso, as bibliotecas de terceiros trabalham regularmente no trabalho com a rede, no cache de dados, na renderização da interface do usuário via JavaScript e muito mais. Em contraste com todas essas tendências,
Pavel Gurov falou sobre a solução arquitetural, que será relevante, independentemente de quais tecnologias você estiver usando agora ou em alguns anos.
O ApplicationCoordinator pode ser usado para criar navegação entre telas e, ao mesmo tempo, resolver vários problemas. Sob a demonstração do gato e instruções para a implementação mais rápida dessa abordagem.
Sobre o palestrante: Pavel Gurov está desenvolvendo aplicativos iOS no Avito.
Navegação

Navegar entre telas é uma tarefa que 100% de vocês enfrentam, não importa o que façam - uma rede social, uma chamada de táxi ou um banco on-line. É assim que o aplicativo começa, mesmo no estágio do protótipo, quando você nem sabe totalmente como serão as telas, que tipo de animação serão, se os dados serão armazenados em cache. As telas podem ser imagens em branco ou estáticas, mas
a tarefa de navegação aparece no aplicativo assim que houver mais de uma dessas telas . Ou seja, quase imediatamente.

Os métodos mais comuns para construir a arquitetura de aplicativos iOS: MVc, MVVm e MVp, descrevem como construir um módulo de tela única. Também diz que os módulos podem se conhecer, se comunicar, etc. Mas pouca atenção é dada às questões de como são feitas as transições entre esses módulos, quem decide essas transições e como os dados são transmitidos.
UlStoryboard + segues
O iOS pronto para uso fornece várias maneiras de mostrar o seguinte cenário de tela:
- O conhecido UlStoryboard + segue , quando designamos todas as transições entre telas em um meta-arquivo e as chamamos. Tudo é muito conveniente e ótimo.
- Contêineres - como o UINavigationController. UITabBarController, UIPageController ou, possivelmente, contêineres auto-escritos que podem ser usados tanto programaticamente quanto em conjunto com o StoryBoards.
- Método presente (_: animado: conclusão :). Este é apenas um método da classe UIController.
Não há problemas com essas ferramentas em si. O problema é exatamente como eles são comumente usados. O método UINavigationController, performSegue, prepareForSegue, presentViewController são todos os métodos de propriedade da classe UIViewController. A Apple sugere o uso dessas ferramentas dentro do próprio UIViewController.

Prova disso é a seguinte.

Esses são os comentários que aparecem no seu projeto se você criar uma nova subclasse de UIViewController usando um modelo padrão. Ele é gravado diretamente - se você usa segues e precisa transferir dados para a próxima tela de acordo com o cenário, você deve: obter este ViewController a partir de segue; sabe que tipo será; faça a transmissão para esse tipo e passe seus dados para lá.
Esta abordagem aos problemas na construção da navegação.
1. Conectividade rígida de telasIsso significa que a tela 1 sabe sobre a existência da tela 2. Ele não apenas sabe sobre sua existência, como também potencialmente a cria, ou retira-a, sabendo que tipo é e transfere alguns dados para ela.
Se, em algumas circunstâncias, precisarmos exibir a tela 3 em vez da tela 2, teremos que conhecer a nova tela 3 da mesma maneira que ser costurada no controlador de tela 1. Tudo se torna ainda mais difícil se os controladores 2 e 3 puderem ser chamados de vários outros lugares, não apenas da tela 1. Acontece que o conhecimento das telas 2 e 3 terá que ser costurado em cada um desses lugares.
Para fazer isso é outra metade do problema, os principais problemas começarão quando for necessário fazer alterações nessas transições ou apoiar tudo isso.
2. Reordene os controladores de scriptIsso também não é tão simples por causa da conexão. Para trocar dois ViewControllers, não será suficiente entrar no UlStoryboard e trocar duas fotos. Você precisará abrir o código para cada uma dessas telas, transferi-lo para as configurações da próxima e alterar seus locais, o que não é muito conveniente.
3. Transferência de dados de acordo com o cenárioPor exemplo, ao escolher algo na tela 3, precisamos atualizar o modo de exibição na tela 1. Como inicialmente não temos nada além de um ViewController, teremos que conectar de alguma forma os dois ViewControllers - não importa como - através da delegação ou de alguma forma ainda. Será ainda mais difícil se, de acordo com a ação na tela 3, for necessário atualizar não uma tela, mas várias ao mesmo tempo, por exemplo, a primeira e a segunda.

Nesse caso, a delegação não pode ser dispensada, porque a delegação é um relacionamento individual. Alguém dirá, vamos usar a notificação, alguém - através de um estado compartilhado. Tudo isso dificulta a depuração e o rastreamento de fluxos de dados em nosso aplicativo.
Como se costuma dizer, é melhor ver uma vez do que ouvir 100 vezes. Vejamos um exemplo específico desse aplicativo Avito Services Pro. Esta aplicação é para profissionais do setor de serviços, nos quais é conveniente rastrear seus pedidos, comunicar-se com os clientes, procurar novos pedidos.
Cenário - escolhendo uma cidade na edição de um perfil de usuário.

Aqui está uma tela de edição de perfil, como em muitos aplicativos. Estamos interessados em escolher uma cidade.
O que está acontecendo aqui?
- O usuário clica na célula com a cidade e a primeira tela decide que é hora de adicionar a seguinte tela à pilha de navegação. Esta é uma tela com uma lista de cidades federais (Moscou e São Petersburgo) e uma lista de regiões.
- Se o usuário selecionar uma cidade federal na segunda tela, a segunda tela entenderá que o script foi concluído, encaminhará a cidade selecionada para a primeira e a pilha Navegação voltará para a primeira tela. O script é considerado completo.
- Se o usuário seleciona uma área na segunda tela, a segunda tela decide que uma terceira tela deve ser preparada, na qual vemos uma lista de cidades nessa área. Se o usuário seleciona uma cidade, essa cidade é enviada para a primeira tela, rola a pilha de Navegação e o script é considerado completo.
Neste diagrama, os problemas de conectividade que mencionei anteriormente são mostrados como setas entre o ViewController. Vamos nos livrar desses problemas agora.
Como fazemos isso?- Nós nos proibimos dentro do UIViewController de acessar contêineres , ou seja, self.navigationController, self.tabBarController ou alguns outros contêineres personalizados que você criou como extensão de propriedade. Agora não podemos pegar nosso contêiner do código da tela e pedir para ele fazer alguma coisa.

- Nós nos proibimos dentro do UIViewController de chamar o método performSegue e escrever código no método prepareForSegue, que pegaria a tela que segue o script e a configuraria. Ou seja, não trabalhamos mais com segue (com transições entre telas) dentro do UIViewController.

- Também proibimos qualquer menção de outros controladores dentro de nosso controlador específico : sem inicializações, transferências de dados e isso é tudo.

Coordenador
Como removemos todas essas responsabilidades do UIViewController, precisamos de uma nova entidade que as cumpra. Crie uma nova classe de objetos e chame-a de coordenadora.

O coordenador é apenas um objeto comum para o qual passamos no início do NavigationController e chamamos o método Start. Agora, não pense em como ela é implementada; basta ver como o cenário para escolher uma cidade mudará nesse caso.
Agora, não começa com o fato de estarmos preparando a transição para qualquer tela NavigationController específica, mas chamamos o método Start no coordenador, passando-o antes disso no inicializador NavigationController. O coordenador entende que é hora do NavigationController iniciar a primeira tela, o que ele faz.
Além disso, quando o usuário seleciona uma célula com uma cidade, esse evento é passado para o coordenador. Ou seja, a própria tela não sabe de nada - depois, como dizem, pelo menos uma inundação. Ele envia essa mensagem ao coordenador e, em seguida, o coordenador reage a isso (já que ele tem um NavigationController), que envia o próximo passo para isso - essa é a escolha das regiões.
Em seguida, o usuário clica em "Região" - exatamente a mesma imagem - a tela em si não resolve nada, apenas informa ao coordenador que a próxima tela será aberta.
Quando o usuário seleciona uma cidade específica na terceira tela, essa cidade também é transferida para a primeira tela através do coordenador. Ou seja, uma mensagem é enviada ao coordenador de que uma cidade foi selecionada. O coordenador envia essa mensagem para a primeira tela e rola a pilha de Navegação para a primeira tela.
Observe que os
controladores não se comunicam mais , decidindo quem será o próximo e não transmitem nenhum dado um ao outro. Além disso, eles não sabem nada sobre o ambiente.

Se considerarmos o aplicativo dentro da estrutura de uma arquitetura de três camadas, o ViewController deve idealmente se encaixar completamente na camada Presentation e transportar o mínimo possível a lógica do aplicativo.
Nesse caso, usamos o coordenador para extrair a lógica das transições para a camada acima e remover esse conhecimento do ViewController.
Demo
Um
projeto de apresentação e demonstração está disponível no Github; abaixo, uma demonstração durante a palestra.
Este é o mesmo cenário: editar um perfil e escolher uma cidade nele.
A primeira tela é a tela de edição do usuário. Ele mostra informações sobre o usuário atual: nome e cidade selecionada. Há um botão "Escolha uma cidade". Quando clicamos nele, chegamos à tela com uma lista de cidades. Se selecionarmos uma cidade lá, a primeira tela exibirá essa cidade.
Vamos ver agora como isso funciona no código. Vamos começar com o modelo.
struct City { let name: String } struct User { let name: String var city: City? }
Os modelos são simples:
- Uma estrutura de cidade que possui um nome de campo, sequência;
- Um usuário que também possui um nome e uma cidade de propriedade.
O próximo é o
StoryBoard . Começa com um NavigationController. Em princípio, aqui estão as mesmas telas que estavam no simulador: uma tela de edição do usuário com um rótulo e um botão e uma tela com uma lista de cidades, que mostra um tablet com cidades.
Tela de edição do usuário
import UIKit final class UserEditViewController: UIViewController, UpdateableWithUser { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? @IBOutlet private weak var userLabel: UILabel? @IBAction private func selectCityTap(_ sender: UIButton) { onSelectCity?() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateView() } private func updateView() { userLabel?.text = "User: \(user?.name ?? ""), \n" + "City: \(user?.city?.name ?? "")" } }
Aqui existe uma propriedade User - este é o usuário que é transmitido para fora - o usuário que editaremos. Definir usuário aqui faz com que o bloco didSet seja chamado, o que leva a uma chamada para o método local updateView (). Tudo o que esse método faz é simplesmente colocar informações sobre o usuário no rótulo, ou seja, mostrar seu nome e o nome da cidade em que esse usuário vive.
O mesmo acontece no método viewWillAppear ().
O local mais interessante é o manipulador para clicar no botão de seleção da cidade, selecionar CityTap ().
Aqui, o próprio controlador não resolve nada : não cria nenhum controlador, não chama segue. Tudo o que ele faz é o retorno de chamada - esta é a segunda propriedade do nosso ViewController. O retorno de chamada onSelectCity não possui parâmetros. Quando o usuário clica no botão, isso faz com que esse retorno de chamada seja chamado.
Tela de seleção da cidade
import UIKit final class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? // MARK: - Private variables - private let cities: [City] = [City(name: "Moscow"), City(name: "Ulyanovsk"), City(name: "New York"), City(name: "Tokyo")] // MARK: - Table - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cities.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = cities[indexPath.row].name return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { onCitySelected?(cities[indexPath.row]) } }
Essa tela é um UITableViewController. A lista de cidades aqui é fixa, mas pode vir de outro lugar. Além disso (// MARCA: - Tabela -) é um código de tabela bastante trivial que exibe uma lista de cidades nas células.
O lugar mais interessante aqui é o manipulador didSelectRowAt IndexPath, um método conhecido para todos. Aqui, a própria tela novamente não resolve nada. O que acontece depois que a cidade é selecionada? Simplesmente chama um retorno de chamada com um único parâmetro "cidade".
Isso termina o código para as próprias telas. Como vemos, eles não sabem nada sobre o ambiente.
Coordenador
Vamos para o link entre essas telas.
import UIKit protocol UpdateableWithUser: class { var user: User? { get set } } final class UserEditCoordinator { // MARK: - Properties private var user: User { didSet { updateInterfaces() } } private weak var navigationController: UINavigationController? // MARK: - Init init(user: User, navigationController: UINavigationController) { self.user = user self.navigationController = navigationController } func start() { showUserEditScreen() } // MARK: - Private implementation private func showUserEditScreen() { let controller = UIStoryboard.makeUserEditController() controller.user = user controller.onSelectCity = { [weak self] in self?.showCitiesScreen() } navigationController?.pushViewController(controller, animated: false) } private func showCitiesScreen() { let controller = UIStoryboard.makeCitiesController() controller.onCitySelected = { [weak self] city in self?.user.city = city _ = self?.navigationController?.popViewController(animated: true) } navigationController?.pushViewController(controller, animated: true) } private func updateInterfaces() { navigationController?.viewControllers.forEach { ($0 as? UpdateableWithUser)?.user = user } } }
O coordenador tem duas propriedades:
- Usuário - o usuário que editaremos;
- O NavigationController ao qual passar na inicialização.
Existe um init () simples que preenche essas propriedades.
A seguir, é apresentado o método start (), que faz com que o método
ShowUserEditScreen () seja
chamado . Vamos insistir nisso com mais detalhes. Este método retira o controlador do UIStoryboard, passa-o para o usuário local. Em seguida, ele coloca o retorno de chamada SelectSity e coloca esse controlador na pilha de navegação.
Depois que o usuário clica no botão, o retorno de chamada onSelectCity é acionado e isso faz com que o seguinte método
ShowCitiesScreen () privado seja
chamado .
Na verdade, ele faz quase a mesma coisa - levanta um controlador ligeiramente diferente do UIStoryboard, coloca o retorno de chamada onCitySelected nele e o empurra para a pilha de Navegação - é tudo o que acontece. Quando o usuário seleciona uma cidade específica, esse retorno de chamada é acionado, o coordenador atualiza o campo "cidade" do usuário local e rola a pilha de Navegação para a primeira tela.
Como o usuário é uma estrutura, a atualização do campo "cidade" leva ao fato de que o bloco didSet é chamado, respectivamente, o método privado
updateInterfaces () . Esse método percorre toda a pilha de navegação e tenta implantar cada ViewController como o protocolo UpdateableWithUser. Este é o protocolo mais simples, que possui apenas uma propriedade - usuário. Se isso der certo, ele o envia ao usuário atualizado. Assim, acontece que nosso usuário selecionado na segunda tela passa automaticamente para a primeira tela.
Tudo está claro com o coordenador, e a única coisa a ser mostrada aqui é o ponto de entrada para o nosso aplicativo. É aqui que tudo começa. Nesse caso, esse é o método didFinishLaunchingWithOptions do nosso AppDelegate.
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var coordinator: UserEditCoordinator! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { guard let navigationController = window?.rootViewController as? UINavigationController else { return true } let user = User(name: "Pavel Gurov", city: City(name: "Moscow")) coordinator = UserEditCoordinator(user: user, navigationController: navigationController) coordinator.start() return true } }
Aqui, o navigationController é retirado do UIStoryboard, um usuário é criado, o qual editaremos, com um nome e uma cidade específica. Em seguida, criamos nosso coordenador com User e navigationController. Ele chama o método start (). O coordenador é transferido para a propriedade local - isso é basicamente tudo. O esquema é bastante simples.
Entradas e saídas
Há vários pontos em que gostaria de me aprofundar em mais detalhes. Você provavelmente notou que a propriedade no userEditViewController está marcada com um comentário como Entrada e os retornos de chamada desses controladores estão marcados como Saída.
Uma entrada é qualquer dado que possa sofrer alterações ao longo do tempo, bem como alguns métodos ViewController que podem ser chamados de fora. Por exemplo, em UserEditViewController, essa é uma propriedade User - o próprio usuário ou seu parâmetro City podem ser alterados.
Uma saída é qualquer evento que o controlador queira se comunicar com o mundo externo. No UserEditViewController, é um clique no botão onSelectCity e, na tela de seleção da cidade, é um clique em uma célula com uma cidade específica. A principal idéia aqui é, repito, que o controlador não sabe nada e não faz nada sobre esses eventos. Ele delega para decidir o que fazer, para outra pessoa.
No Objective-C, não gostei muito de escrever callbacks salvos por causa de sua terrível sintaxe. Mas em Swift, isso é muito mais simples. O uso de retornos de chamada nesse caso é uma alternativa ao padrão de delegação conhecido no iOS. Somente aqui, em vez de designar métodos no protocolo e dizer que o coordenador corresponde a esse protocolo e, em seguida, escrever esses métodos em algum lugar separadamente, podemos imediatamente criar uma entidade de maneira conveniente e conveniente, colocar um retorno de chamada e fazer tudo.
É verdade que, com essa abordagem, diferentemente da delegação, há uma conexão estreita entre a essência do coordenador e a tela, porque o coordenador sabe que existe uma essência específica da tela.
Você pode se livrar disso da mesma maneira que na delegação, usando protocolos.

Para evitar a conectividade, podemos
fechar a entrada e a saída do nosso controlador com um protocolo .
Acima está o protocolo CitiesOutput, que tem exatamente um requisito - o retorno de chamada onCitySelected. À esquerda, é um análogo desse esquema no Swift. Nosso controlador está em conformidade com este protocolo, determinando o retorno de chamada necessário. Fazemos isso para que o coordenador não saiba sobre a existência da classe CitiesViewController. Mas em algum momento ele precisará configurar a saída desse controlador. Para aumentar tudo, adicionamos uma fábrica ao coordenador.

A fábrica possui um método cityOutput (). Acontece que nosso coordenador não cria um controlador e não o obtém de algum lugar. Uma fábrica lança para ele, que retorna um objeto fechado pelo protocolo no método, e ele não sabe nada sobre qual classe esse objeto é.
Agora, a coisa mais importante - por que tudo isso?
Por que precisamos construir em outro nível adicional quando não havia problemas?Pode-se imaginar esta situação: um gerente nos procurará e pedirá que você faça um teste A / B do fato de que, em vez de uma lista de cidades, teríamos a opção de escolher uma cidade no mapa. Se em nossa aplicação a escolha da cidade não estava em um lugar, mas em coordenadores diferentes, em diferentes cenários, tivemos que costurar uma bandeira em cada lugar, jogá-la para fora, nessa bandeira, levantar um ou outro ViewController. Isso não é muito conveniente.
Queremos remover esse conhecimento do coordenador. Portanto, alguém poderia fazer isso em um só lugar. Na própria fábrica, criaríamos um parâmetro pelo qual a fábrica retornasse um ou outro controlador fechado pelo protocolo. Ambos teriam um retorno de chamada em CitySelected, e o coordenador, em princípio, não se importaria com qual dessas telas trabalhar - um mapa ou uma lista.
Composição VS Herança
O próximo ponto em que eu queria me concentrar é a composição contra herança.

- O primeiro método de como nosso coordenador pode ser feito é fazer a composição quando o NavigationController for passado a ele de fora e armazenado localmente como propriedade. É como uma composição - adicionamos um NavigationController como uma propriedade.
- Por outro lado, existe uma opinião de que tudo está presente no Kit da interface do usuário e não precisamos reinventar a roda. Você pode simplesmente pegar e herdar o UI NavigationController .
Cada opção tem seus prós e contras, mas, pessoalmente, parece-me que a
composição nesse caso é mais adequada do que a herança. A herança geralmente é um esquema menos flexível. Se precisarmos, por exemplo, alterar o Navigation para, digamos, UIPageController, então, no primeiro caso, podemos simplesmente fechá-los com um protocolo comum, como “Mostrar a próxima tela” e substituir convenientemente o contêiner de que precisamos.
Do meu ponto de vista, o argumento mais importante é que você oculte do usuário final na composição todos os métodos desnecessários. Acontece que ele é menos propenso a tropeçar. Você deixa
apenas a API necessária , por exemplo, o método Start - e é tudo. Ele não tem como chamar o método PushViewController, PopViewController, ou seja, interferir de alguma forma nas atividades do coordenador. Todos os métodos da classe pai estão ocultos.
Storyboards
Acredito que eles merecem atenção especial junto com os seguidores. Pessoalmente,
apoio os seguidores , pois eles permitem que você se familiarize rapidamente com o script. Quando um novo desenvolvedor chega, ele não precisa escalar o código, os Storyboards ajudam nisso. Mesmo se você criar uma interface com o código, poderá deixar o ViewController vazio e criar a interface com o código, mas deixe pelo menos as transições e o ponto inteiro. Toda a essência do Storyboards está nas próprias transições e não no layout da interface do usuário.
Felizmente, a
abordagem do
coordenador não limita a escolha das ferramentas . Podemos usar com segurança coordenadores junto com segues. Mas devemos lembrar que agora não podemos trabalhar com segues dentro do UIViewController.

Portanto, devemos substituir o método onPrepareForSegue em nossa classe. Em vez de fazer algo dentro do controlador, delegaremos essas tarefas novamente ao coordenador, através do retorno de chamada. O método onPrepareForSegue é chamado, você não faz nada por conta própria - você não sabe que tipo de acompanhamento é esse, que controlador de destino é - não importa para você. Você simplesmente joga tudo em um retorno de chamada, e o coordenador descobrirá. Ele tem esse conhecimento, você não precisa desse conhecimento.
Para simplificar tudo, você pode fazer isso em uma determinada classe Base para não substituí-lo em cada controlador tomado separadamente. Nesse caso, será mais conveniente para o coordenador trabalhar com seus seguidores.
Outra coisa que acho conveniente com o Storyboard é aderir à regra de que
um Storyboard é igual a um coordenador . Depois, você pode simplificar bastante tudo, criar uma classe em geral - o StoryboardCoordinator e gerar o parâmetro RootType nele, criar o controlador de Navegação inicial no Storyboard e agrupar todo o script nele.

Como você pode ver, aqui o coordenador possui 2 propriedades: navigationController; O rootViewController do nosso RootType é genérico. Durante a inicialização, passamos para ele não um navigationController específico, mas um Storyboard, do qual nossa Navegação raiz e seu primeiro controlador obtêm. Dessa forma, nem precisamos chamar nenhum método Start. Ou seja, você criou um coordenador, ele imediatamente tem Navegação e imediatamente Raiz. Você pode mostrar a navegação modalmente ou pegar o Root e acessar a navegação existente e continuar trabalhando.
Nosso UserEditCoordinator nesse caso simplesmente se tornaria tipealias, substituindo o tipo de seu RootViewController no parâmetro genérico.
Transferência de dados de script de volta
Vamos falar sobre a solução do último problema, que descrevi no início. Essa é a transferência de dados de volta para o script.

Considere o mesmo cenário para escolher uma cidade, mas agora será possível escolher não uma cidade, mas várias. Para mostrar ao usuário que ele selecionou várias cidades da mesma região, mostraremos na tela com uma lista de regiões um pequeno número ao lado do nome da região, mostrando o número de cidades selecionadas nessa região.
Acontece que a ação em um controlador (no terceiro) deve levar a uma mudança na aparência de vários outros ao mesmo tempo. Ou seja, no primeiro devemos mostrar na célula com a cidade e no segundo devemos atualizar todos os números nas regiões selecionadas.
O coordenador simplifica essa tarefa transferindo dados de volta para o script - agora é uma tarefa tão simples quanto transferir dados para frente de acordo com o script.
O que está acontecendo aqui? O usuário seleciona uma cidade. Esta mensagem é enviada ao coordenador. O coordenador, como já mostrei na demonstração, percorre toda a pilha de navegação e envia dados atualizados para todas as partes interessadas. Consequentemente, o ViewController pode atualizar sua visualização com esses dados.
Refatorando o código existente
Como refatorar o código existente, se você deseja incorporar essa abordagem em um aplicativo existente que possui MVc, MVVm ou MVp?

Você tem um monte de ViewController. A primeira coisa a fazer é dividi-los em cenários em que participam. No nosso exemplo, existem três cenários: autorização, edição de perfil, fita.

Agora, agrupamos cada cenário dentro de nosso coordenador. De fato, devemos poder iniciar esses scripts de qualquer lugar em nosso aplicativo. Isso deve ser flexível - o
coordenador deve ser completamente auto-suficiente .
Essa abordagem de desenvolvimento oferece maior comodidade. Consiste no fato de que, se você estiver trabalhando atualmente com um cenário específico, não precisará clicar nele toda vez que o iniciar. Você pode iniciá-lo rapidamente no início, editar algo nele e remover essa inicialização temporária.
Depois de decidirmos sobre nossos coordenadores, precisamos determinar qual cenário pode levar ao início de outro e criar uma árvore a partir desses cenários.

No nosso caso, a árvore é simples: o LoginCoordinator pode iniciar o coordenador de edição de perfis. Aqui, quase tudo se encaixa, mas um detalhe muito importante permanece - nosso esquema carece de um ponto de entrada.

Este ponto de entrada será um coordenador especial -
ApplicationCoordinator . Ele é criado e iniciado pelo
AppDelegate e, em seguida, já controla a lógica no nível do aplicativo, ou seja, qual coordenador inicia agora.
Acabamos de analisar um circuito muito semelhante, mas ele tinha o ViewController em vez de coordenadores, e fizemos com que o ViewController não soubesse nada um do outro e não passasse dados entre si. Em princípio, o mesmo pode ser feito com os coordenadores. Podemos designar uma determinada entrada (método Start) e Output (retorno de chamada onFinish) nelas.
Os coordenadores se tornam independentes, reutilizáveis e facilmente testáveis . Os coordenadores deixam de se conhecer e se comunicam, por exemplo, apenas com o ApplicationCoordinator.
Você precisa ter cuidado, porque se o seu aplicativo tiver o suficiente desses scripts, o ApplicationCoordinator poderá se transformar em um grande objeto divino, ele saberá sobre todos os scripts existentes - isso também não é muito legal. Aqui já devemos procurar - talvez dividir os coordenadores em sub-coordenadores, ou seja, pensar em uma arquitetura como essa para que esses objetos não atinjam tamanhos incríveis.
Embora o tamanho nem sempre seja um motivo para refatoração .
Por onde começar
Aconselho começar de baixo para cima - primeiro implemente scripts individuais.

Como solução alternativa, eles podem ser iniciados dentro do UIViewController. Ou seja, desde que você não tenha o Root ou outros coordenadores, você pode criar um coordenador e, como solução temporária, iniciá-lo no UIViewController, salvando-o localmente na propriedade (como o nextCoordinator está acima). Quando um evento ocorre, você, como mostrei na demonstração, cria uma propriedade local, coloca o coordenador lá e chama o método Start nela. Tudo é muito simples.
Então, quando todos esses coordenadores já tiverem feito, o início de um dentro do outro será exatamente o mesmo. Você tem uma propriedade local ou algum tipo de matriz de dependências, como coordenador, coloca tudo isso lá para que ele não fuja e chama o método Start.
Sumário
- Telas e scripts independentes que não sabem nada um do outro não se comunicam. Tentamos conseguir isso.
- É fácil alterar a ordem das telas no aplicativo sem alterar os códigos de tela. Se tudo for feito como deveria, a única coisa que deve mudar no aplicativo quando o script muda não é o código da tela, mas o código do coordenador.
- Transferência de dados simplificada entre telas e outras tarefas que implicam uma conexão entre telas.
- — , .
AppsConf 2018 8 9 — ! ( ) . — iOS Android, , , , .