A Injeção de Dependências é um padrão bastante popular que permite configurar o sistema de maneira flexível e criar corretamente as dependências dos componentes deste sistema. Graças à digitação, o Swift permite usar estruturas convenientes com as quais você pode descrever brevemente o gráfico de dependência. Hoje eu quero falar um pouco sobre um desses frameworks - DITranquillity .
Este tutorial abordará os seguintes recursos da biblioteca:
- Registo de tipo
- Implantação de inicialização
- Incorporando em uma variável
- Dependências cíclicas de componentes
- Usando a biblioteca com
UIStoryboard
Descrição do componente
O aplicativo consistirá nos seguintes componentes principais: ViewController , Router , Presenter , Networking - esses são componentes bastante comuns em qualquer aplicativo iOS.
ViewController e o Router serão introduzidos um no outro ciclicamente.
Preparação
Primeiro, crie um Aplicativo de exibição única no Xcode, adicione DITranquillity usando CocoaPods . Crie a hierarquia necessária de arquivos, adicione um segundo controlador ao Main.storyboard e conecte-o usando o StoryboardSegue . Como resultado, a seguinte estrutura de arquivo deve ser obtida:
Crie dependências nas classes da seguinte maneira:
Declaração de componente protocol Presenter: class { func getCounter(completion: @escaping (Int) -> Void) } class MyPresenter: Presenter { private let networking: Networking init(networking: Networking) { self.networking = networking } func getCounter(completion: @escaping (Int) -> Void) {
protocol Networking: class { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) } class MyNetworking: Networking { func fetchData(completion: @escaping (Result<Int, Error>) -> Void) {
protocol Router: class { func presentNewController() } class MyRouter: Router { unowned let viewController: ViewController init(viewController: ViewController) { self.viewController = viewController } func presentNewController() {
class ViewController: UIViewController { var presenter: Presenter! var router: Router! }
Limitações
Diferentemente de outras classes, o ViewController não é criado por nós, mas pela biblioteca UIKit dentro da implementação UIStoryboard.instantiateViewController , portanto, usando o storyboard, não podemos injetar dependências nos herdeiros do UIViewController usando o inicializador. O mesmo acontece com os herdeiros do UIView e do UITableViewCell .
Observe que os objetos ocultos atrás dos protocolos são incorporados em todas as classes. Essa é uma das principais tarefas da implementação de dependências - criar dependências não nas implementações, mas nas interfaces. Isso ajudará no futuro a fornecer diferentes implementações de protocolo para reutilizar ou testar componentes.
Injeção de Dependência
Depois que todos os componentes do sistema são criados, prosseguimos para a conexão dos objetos entre si. No DITranquillity, o ponto de partida é o DIContainer , que adiciona o registro usando o método container.register(...) . Para separar as dependências em partes, DIFramework e DIPart , os quais devem ser implementados. Por conveniência, criaremos apenas uma classe ApplicationDependency , que implementará o DIFramework e servirá como local de registro para todas as dependências. A interface DIFramework requer que você implemente apenas um método - load(container:) .
class ApplicationDependency: DIFramework { static func load(container: DIContainer) {
Vamos começar com a inscrição mais simples que não tem dependências - MyNetworking
container.register(MyNetworking.init)
Este registro usa a implementação através do inicializador. Apesar do fato de o componente em si não ter dependências, o inicializador deve ser fornecido para deixar claro para a biblioteca como criar o componente.
Da mesma forma, registre MyPresenter e MyRouter .
container.register1(MyPresenter.init) container.register1(MyRouter.init)
Nota: Observe que não é o register usado, mas o register1 . Infelizmente, isso é necessário para indicar se o objeto possui uma e apenas uma dependência no inicializador. Ou seja, se houver 0 ou duas ou mais dependências, você só precisará usar o register . Essa restrição é um bug do Swift versão 4.0 e superior.
É hora de registrar nosso ViewController . Ele injeta objetos não através do inicializador, mas diretamente na variável, portanto a descrição do registro será um pouco mais.
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter)
A sintaxe do formulário \.presenter é SwiftKeyPath, graças à qual é possível implementar de forma concisa as dependências. Como o Router e o ViewController ciclicamente dependentes um do outro, você deve indicar explicitamente isso usando cycle: true . A própria biblioteca pode resolver essas dependências sem uma indicação explícita, mas esse requisito foi introduzido para que a pessoa que está lendo o gráfico entenda imediatamente que existem ciclos na cadeia de dependências. Observe também que NOT ViewController.init , mas ViewController.self . Isso foi descrito acima na seção Limitações .
Você também deve registrar o UIStoryboard usando um método especial.
container.registerStoryboard(name: "Main")
Agora, descrevemos todo o gráfico de dependência para uma tela. Mas ainda não há acesso a este gráfico. Você deve criar um DIContainer que permita acessar os objetos nele.
static let container: DIContainer = { let container = DIContainer()
- Inicialize o contêiner
- Adicione uma descrição do gráfico a ele.
- Verificamos que fizemos tudo certo. Se um erro for cometido, o aplicativo falhará não durante a resolução de dependências, mas imediatamente ao criar um gráfico
Então você precisa tornar o contêiner o ponto de partida do aplicativo. Para fazer isso, implementamos o método didFinishLaunchingWithOptions no didFinishLaunchingWithOptions em vez de especificar Main.storyboard como o ponto de Main.storyboard nas configurações do projeto.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) let storyboard: UIStoryboard = ApplicationDependency.container.resolve() window?.rootViewController = storyboard.instantiateInitialViewController() window?.makeKeyAndVisible() return true }
Lançamento
No primeiro início, ocorrerá uma queda e a validação falhará pelos seguintes motivos:
- O contêiner não encontrará os tipos
Router , Presenter , Networking , porque registramos apenas objetos. Se queremos dar acesso não a implementações, mas a interfaces, devemos especificar explicitamente as interfaces - O contêiner não entende como resolver a dependência cíclica, porque é necessário indicar explicitamente quais objetos não devem ser recriados toda vez que o gráfico for resolvido.
É simples corrigir o primeiro erro - existe um método especial que permite especificar em quais protocolos o método no contêiner está disponível.
container.register(MyNetworking.init) .as(check: Networking.self) {$0}
Descrevendo o registro da seguinte forma, dizemos: o objeto MyNetworking ser acessado via protocolo de Networking . Isso deve ser feito para todos os objetos ocultos nos protocolos. {$0} para a verificação correta dos tipos pelo compilador.
O segundo erro é um pouco mais complicado. É necessário usar o chamado scope , que descreve com que frequência o objeto é criado e quanto vive. Para cada registro participando de uma dependência circular, você deve especificar um scope igual a objectGraph . Isso deixará claro para o contêiner que, durante a resolução, é necessário reutilizar os mesmos objetos criados, em vez de criar a cada vez. Assim, verifica-se:
container.register(ViewController.self) .injection(cycle: true, \.router) .injection(\.presenter) .lifetime(.objectGraph) container.register1(MyRouter.init) .as(check: Router.self) {$0} .lifetime(.objectGraph)
Após reiniciar, o contêiner passa na validação com êxito e nosso ViewController é aberto com as dependências criadas. Você pode colocar um ponto de interrupção no viewDidLoad e garantir.
Transição entre telas
Em seguida, crie duas classes pequenas, SecondViewController e SecondPresenter , adicione SecondViewController ao storyboard e crie um Segue entre elas com o identificador "RouteToSecond" , que permite abrir o segundo controlador a partir do primeiro.
Adicione mais dois registros ao nosso ApplicationDependency para cada uma das novas classes:
container.register(SecondViewController.self) .injection(\.secondPresenter) container.register(SecondPresenter.init)
.as há necessidade de especificar .as , porque não SecondPresenter o SecondPresenter por trás do protocolo, mas usamos a implementação diretamente. Então, no método viewDidAppear do primeiro controlador, chamamos performSegue(withIdentifier: "RouteToSecond", sender: self) , start, o segundo controlador é aberto, no qual a dependência do secondPresenter deve ser afixada. Como você pode ver, o contêiner viu a criação de um segundo controlador a partir do UIStoryboard e UIStoryboard êxito as dependências.
Conclusão
Essa biblioteca permite que você trabalhe convenientemente com dependências cíclicas, storyboards e use totalmente a inferência automática de tipo no Swift, que fornece uma sintaxe muito curta e flexível para descrever o gráfico de dependência.
Referências
Código de amostra completo na biblioteca do github
DITranquillity no github
Artigo em inglês