Roteador e dados passando a arquitetura Clean Swift

Olá leitor!

Em um artigo anterior , falei sobre o ciclo VIP da arquitetura Clean Swift . Agora, abordaremos um dos tópicos mais importantes - a transição e a transferência de dados entre cenas.



Teoria


O componente Router é responsável pela lógica de navegação e transferência de dados, que faz parte da cena (opcional, é claro). É inicializado no ViewController , junto com o Interactor e o Presenter .

O roteador implementa dois protocolos - RoutingLogic e DataPassing , que preencheremos com nossa funcionalidade. RoutingLogic deve conter os métodos responsáveis ​​pela transição para uma cena específica. DataPassing contém a variável dataStore , que se refere ao Protocolo DataStore . O Interactor Scenes implementa o protocolo DataStore e trabalha com as variáveis ​​armazenadas nele. O próprio roteador contém um link para o ViewController de sua cena.

Usando o link para o ViewController , o roteador salta entre as cenas. Para fazer isso, você pode usar o Segue ou criar uma cena para a qual deseja fazer uma transição programaticamente. Qual método usado não é importante, o principal para nós é ter um link para uma instância da classe ViewController para a qual estamos mudando.

Usando o link para o DataStore, transferiremos dados do Interator de uma cena para o Interactor da cena para a qual estamos mudando. E, como mencionado anteriormente, é o roteador que precisa saber como fazer isso.

Prática


Por exemplo, transferiremos o texto do TextField para o Label de outra cena. Vamos considerar duas maneiras de transição entre cenas - de acordo com Segue e programaticamente.

A classe Router contém 3 grupos de métodos semânticos:

  1. Métodos da implementação RoutingLogic (routeTo)
  2. Métodos responsáveis ​​pela navegação (navigateTo, transição sem Segue)
  3. Métodos para transferência de dados (passDataTo, se houver dados para transferência)



Se fizermos uma transição pelo Segue , por exemplo, quando um botão é clicado, no ViewController , devemos substituir o método prepare (for: sender :). Esta extensão permitirá que você chame automaticamente métodos do roteador com o nome de Segue .

A substituição da preparação (para: remetente :) é opcional ao trabalhar com o Segue. Você pode excluí-lo do código e chamar performSegue (withIdentifier: sender :) no método Router . A preparação é necessária apenas se você precisar usar o Segue junto com a transferência de dados.

final class HomeViewController: UIViewController {
// ...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Segue
if let scene = segue.identifier {
// , Router
// router?.routeToNAME(segue:)
let selector = NSSelectorFromString("routeTo\(scene)WithSegue:")
// ,
// Segue
if let router = router, router.responds(to: selector) {
router.perform(selector, with: segue)
}
}
}
// ...
}


Agora finalmente chegamos à coisa mais interessante - o código do roteador . O exemplo contém comentários, portanto, consideraremos apenas os principais pontos.

Neste roteador, trabalhamos com duas cenas - Casa e Detalhe . A transição da cena inicial também é tratada de duas maneiras - por Segue e programaticamente . Os dados são transferidos da cena inicial para a cena detalhada .

Todos os métodos no protocolo RoutingLogic devem ser nomeados de acordo com o princípio routeToNAME , em que NAME é o nome do Segue (Identificador) que especificamos ao trabalhar com o Storyboard . Isso é necessário não apenas para a conveniência e a beleza do uso, mas também para o nosso seletor de métodos em prepare (para: remetente :) ViewController , que redefinimos anteriormente.

Também na classe HomeRouter, existem métodos que começam com navigateTo e passDataTo . Os primeiros são responsáveis ​​pela lógica de transição, enquanto os últimos são responsáveis ​​pela transferência de dados. Os métodos navigateTo são criados apenas se a transição for feita programaticamente.

No exemplo, temos um método routeToDetail (segue :) . O parâmetro segue é opcional, pois o método contém uma implementação que permite chamá-lo sem usar o Segue . Nos dois casos de transição, obtemos valores não opcionais da cena HomeViewController'a e HomeDataStore'a Home , bem como links para a cena ViewController e DataStore Detail . Aqui vale a pena prestar atenção ao fato de que detailDS é uma variável e é passada para o método passDataToDetail usando o parâmetro pass- through (inout). Isso é importante porque sem o inout, teremos de marcar todos os protocolos do DataStore como “possíveis de implementar apenas com classes” (protocolo DetailDataStore: class), e isso implica muitas dificuldades, incluindo a captura de links fortes.

import UIKit
/// @objc
/// prepare View Controller'e
@objc protocol HomeRoutingLogic {
/// Detail View Controller
func routeToDetail(segue: UIStoryboardSegue?)
}
protocol HomeDataPassing {
var dataStore: HomeDataStore? { get }
}
final class HomeRouter: NSObject, HomeRoutingLogic, HomeDataPassing {
// MARK: - Private
// MARK: - Public
weak var viewController: HomeViewController?
var dataStore: HomeDataStore?
// MARK: - HomeRoutingLogic
func routeToDetail(segue: UIStoryboardSegue?) {
if let segue = segue {
//
// Segue
// Detail View Controller
// Data Store Router'e
guard
let homeDS = dataStore,
let detailVC = segue.destination as? DetailViewController,
var detailDS = detailVC.router?.dataStore
else { fatalError("Fail route to detail") }
// , , ,
// ""
passDataToDetail(source: homeDS, destination: &detailDS)
} else {
// ,
// Segue
// Detail View Controller Storyboard'a
// Data Store Router'e
guard
let viewController = viewController,
let homeDS = dataStore,
let storyboard = viewController.storyboard,
let detailVC = storyboard.instantiateViewController(withIdentifier: "Detail") as? DetailViewController,
var detailDS = detailVC.router?.dataStore
else { fatalError("Fail route to detail") }
passDataToDetail(source: homeDS, destination: &detailDS)
// , ""
navigateToDetail(source: viewController, destination: detailVC)
}
}
// MARK: - Navigation
private func navigateToDetail(source: HomeViewController, destination: DetailViewController) {
source.navigationController?.pushViewController(destination, animated: true)
}
// MARK: - Passing data
/// destination inout,
/// Data Store
private func passDataToDetail(source: HomeDataStore, destination: inout DetailDataStore) {
// HomeDataStore DetailDataStore
destination.message = source.message
}
}


Conclusão


Só isso. Obrigado por ler até o fim! Abaixo deixarei um link para o projeto, se você quiser experimentar o artigo em ação.

Série de artigos


  1. Visão geral da arquitetura Clean Swift
  2. Roteador e transmissão de dados na arquitetura Clean Swift (você está aqui)
  3. Trabalhadores da arquitetura Clean Swift
  4. Teste de unidade na arquitetura Clean Swift
  5. Um exemplo de uma arquitetura simples de loja online Clean Swift

Link para o projeto
Ajuda para escrever um artigo: Bastien

Source: https://habr.com/ru/post/pt454032/


All Articles