Este artigo foi originalmente publicado no EquinociOS. Veja aqui.
A história inicial
Nos últimos pouco mais de dois anos, a Concrete vem desenvolvendo diversos aplicativos para as maiores empresas do Brasil. Nós fomos, inicialmente, responsáveis por reescrever como código nativo e melhorar os dois principais aplicativos desse cliente, pois as versões iniciais eram desenvolvidas em uma tecnologia híbrida.
Para facilitar a nossa vida e por questões de segurança (menos pessoas conhecendo áreas críticas do cliente), resolvemos criar um SDK/Framework com as partes comuns aos aplicativos. Os projetos cresceram, o SDK cresceu, os times aumentaram em tamanho e em número e, é claro, a complexidade aumentou em todas as pontas. Precisávamos novamente simplificar a nossa vida.
Eu passei um tempo em um time responsável por manter o funcionamento do fluxo e desenvolvimento, que garante coisas como documentação de coding style, cobertura de código, qualidade de código, merges solicitados pelos diversos times, entre outras coisas da qualidade geral do projeto, de um dos aplicativos (temos outro time responsável pelo menos no outro grande aplicativo), mas atualmente estou no time do SDK.
Esse é um projeto menor, tocado por menos pessoas, mas bastante complexo, inicialmente desenvolvido por apenas um time, mas que já engloba dois (o meu time e um time de outra consultoria, especializada em segurança), com um terceiro entrando em breve.
Por questões estratégicas, normalmente clientes desse tamanho não querem/podem depender de uma única consultoria, e ao longo do tempo algumas consultorias foram adicionadas ao projeto. Com o aumento da complexidade dos projetos e da quantidade de pessoas e empresas – com culturas e opiniões diferentes sobre coisas como arquitetura dos projetos – algo precisava ser feito para manter a sanidade dos times. Modularizar apenas com um SDK comum não era suficiente.
Além disso, precisei fazer uma prova de conceito usando o SDK e esbarrei em um problema: ele depende do UIKit. Por ser uma base de código menor, resolvemos modularizar ainda mais, começando pelo SDK.
Cenário inicial do SDK
Como disse antes, o SDK é comum a vários apps gerenciados pelo meu time (a parte core) com outro time de outra consultoria desenvolvendo pequenas bibliotecas da parte de segurança do aplicativo, que são enviadas para nós como bibliotecas linkadas estaticamente ao nosso SDK. Esporadicamente, algumas pessoas direto do cliente ainda fazem um bypass no nosso time/processo em pequenas partes do projeto, para adicionar uma feature para um dos projetos principais, mas esquecendo do outro, causando bug (identificado rapidamente pelos times do outro aplicativo ainda em desenvolvimento). E está entrando um time de outra consultoria para mexer no SDK.
Os aplicativos atualmente suportam o iOS 7, e isso exige que a distribuição do SDK seja como biblioteca para linkagem estática. Já temos aplicativos sendo desenvolvidos com suporte apenas ao iOS 9+, sendo escrito em Swift, mas como dependências em Swift devem ser frameworks dinâmicos, esse projeto teve inicialmente que usar CocoaPods para adicionar o SDK estaticamente, com o Carthage linkando dinamicamente as dependências de terceiros escritas em Swift. Como time de devops do cliente barrou o Carthage – por enquanto – sobrou apenas CocoaPods.
O SDK atual compreende, então, três grandes áreas, todas distribuídas como um “framework” – não no sentido de framework dinâmico criado pelo Xcode, mas em uma estrutura similar criada via script, que embute os assets (storyboards, XIBs e imagens) e funcionalidades das três áreas abaixo:
- Comunicação
- Segurança
- UI Comum aos projetos
Além disso, o time do SDK mantém um projeto Sample, para que os times dos apps saibam como usá-lo. Esse sample tem o código-fonte aberto internamente, mas o SDK é distribuído compilado.
SDK “atual”
Além dos problemas acima, esbarramos em um novo recentemente. Um grande redesign está ocorrendo nos apps, melhorando o design e a usabilidade geral, mas não serão lançados ao mesmo tempo. Como o SDK também inclui telas, agora temos as telas antigas, que deverão ter suporte, e as novas em desenvolvimento. Uma solução paleativa foi criada: criar um proxy que determina qual a interface usada. Então, em um exemplo simples, se tínhamos uma classe/tela LoginViewController, tivemos que adicionar a “nova” LoginViewControllerNew, e o proxy determina quem será usado. Além de duplicar classes, temos também assets duplicados. E como pretendemos flexibilizar o SDK, poderia dar à área de negócios a decisão de lançar para outras plataformas Apple (watchOS, tvOS, macOS), hoje estamos intimamente “presos” ao UIKit.
Nossa proposta
Pensamos, então, em separar os três componentes em 3 bibliotecas:
- Core: menor base de código e mais simples de manutenção;
- Segurança: poderá ser mantida por outro time/consultoria;
- UI: com versionamento do design simplificado, podendo criar novas “versões” com interfaces para macOS e watchOS.
Para iniciar a modularização, resolvemos focar em separar inicialmente apenas a parte de UI, que agora passará a ter duas versões major: a versão 1.0.0 seria a versão atual, e a versão 2.0.0 será a versão do redesign.
Futuramente, pretendemos modularizar as maiores aplicações, o que eliminará o merge hell. Isso vai facilitar a verificação de pontos críticos e feature problemáticas e proporcionar uma melhor granulação dos indicadores da cobertura de qualidade, facilitando a manutenção, administração e evolução do produto.
Nossa proposta, então, é tornar o SDK independente da UI, podendo ser criado um aplicativo Sample contendo apenas as funcionalidades “core”, como abaixo:
Ou um Sample que inclui a interface, com a biblioteca de UI tendo a biblioteca core como dependência via CocoaPods.
Primeiro passo: separar a UI
O processo geral – já efetuado – pode ser descrito de forma bastante simples. Foi:
- Criado um novo projeto, que contém apenas código de UI e assets;
- Movido código de UI para novo projeto (mas inicialmente não foi movido o código de testes de UI);
- Removido TODOS os arquivos de UI do projeto core e eliminada a dependência do UIKit;
- Removido Sample (que está se tornando um novo “app”, já em desenvolvimento) do workspace do projeto do SDK.
Mas temos aqui um problema: algumas telas incluídas no core são chamadas por métodos do core, não pelos apps principais. Por exemplo, alguns erros de comunicação já são exibidos para o usuário via chamada do core. Mas o core não pode depender de UI. Além disso, para times que usam a nova biblioteca de UI, desejamos que a comunicação seja transparente e sem configuração.
Como ter duas bibliotecas se comunicando, com o core chamando métodos de UI sem saber da existência desses ou do UIKit?
Protocolos, ao resgate
Na nossa solução, a biblioteca core define alguns protocolos de exibição de interface que são, então, adotados pela biblioteca de UI – que tem o core como dependência.
Mas queríamos que a biblioteca de UI fosse apenas adicionada ao Podfile dos aplicativos. Não queria ter que adicionar ao SDK a responsabilidade de registrar as classes de UI, nem ao desenvolvedor dos aplicativos chamar algum método de configuração. Alguns truques de runtime do Objective-C (válidos em Swift, se a classe herda de NSObject) podem nos ajudar com isso. Mas antes…
Pequeno desvio: inicialização de objetos, bibliotecas e frameworks
Para melhor entender a solução, vou explicar um pouco dobre o processo de inicialização de bibliotecas e de objetos em Objective-C.
Inicialização de objetos
Praticamente toda classe da Foundation e do UIKit herda de NSObject (com exceção de algumas que herdam de NSProxy, mas não vem ao caso para nossa solução). E classes que herdam de NSObject podem implementar dois métodos que podem ser bastante úteis nesse problema. Para entender melhor os dois, recomendo altamente o artigo Friday Q&A 2009-05-22: Objective-C Class Loading and Initialization, do Mike Ash (inclusive, recomendo fortemente o blog dele como um todo. ele está meio parado, mas o conteúdo é riquíssimo).
- +load[
- esse método é chamado quando a biblioteca é carregada em memória. Se implementar numa classe de seu aplicativo, ele será chamado na inicialização, mesmo que a classe nunca seja referenciada durante o ciclo de vida do app. Por exemplo, se você tem uma classe que cuida de impressão, mas o usuário nunca mandar imprimir nada, e você implementa esse método nessa classe, ainda, sim, o método será chamado.
- Se o método é implementado em uma biblioteca de terceiros, depende do tipo de linkagem da biblioteca com seu aplicativo:
- linkagem estática: na inicialização
- linkagem dinâmica: na primeira chamada de um método da biblioteca
- +initialize
- Esse método é um pouco mais lazy, e só é chamado quando o primeiro método da classe também for. Assim, se durante a vida da aplicação uma referência qualquer – chamada de método de classe, instância ou algo como NSStringFromClass ou NSClassFromString – não for chamada, ele não é chamado. Além disso, é garantido pelo runtime que esse método será chamado apenas uma vez por class, na primeira referência da mesma por terceiros.
O método +load tem ainda uma característica especial em relação ao runtime: se a classe e uma ou mais categorias implementarem o método +load, todos serão executados
Observação sobre initialize em Swift – Xcode 8.3 beta 3
O documento de Release Notes do Xcode 8.3 beta 3 inclui a seguinte observação:
Swift will now warn when an NSObject subclass attempts to override the class initialize method. Swift doesn’t guarantee that references to class names trigger Objective-C class realization if they have no other side effects, leading to bugs when Swift code attempts to override initialize. (28954946)
É bom tomar cuidado com isso, caso esteja usando Swift, e pensar em uma implementação parecida. E já que temos falado um pouco sobre linkagem estática e dinâmica, bibliotecas e frameworks, vamos explicar um pouco sobre elas.
“Linkagem” estática vs dinâmica
Linkar uma biblioteca ou framework ao seu projeto funciona como a forma mais básica de gerenciamento de dependências e distribuição/reutilização de código – seu ou de terceiros – em múltiplos projetos. Mas a forma como você linka a biblioteca ou framework impacta no tamanho do executável, como a biblioteca ou framework é distribuído e em que momento eles são carregados em memória e seu conteúdo é executado.
Linkagem estática
Vamos começar explicando a linkagem estática. Nela, o conteúdo executável da biblioteca ou framework que você está linkando ao seu projeto é anexado diretamente ao binário final. No caso de um aplicativo iOS, por exemplo, não é ao arquivo meuapp.ipa, mas ao executável embutido dentro do arquivo ipa. Para quem não sabe, o arquivo ipa é na verdade um arquivo comprimido que contém seu aplicativo, informações da App Store e de code signing. Você pode alterar a extensão para .zip, descomprimir e abrir a pasta Payload, e lá estará seu aplicativo em um pacote com a extensão .app. Se clicar com o direito e selecionar a opção “Mostrar conteúdo do pacote”, vai encontrar dentro desta pasta coisa como assets, uma pasta chamada _CodeSignature com a assinatura/hash dos arquivos embutidos no pacote (para anti-tampering, e evitar que arquivos sejam modificados, embutindo código malicioso) e seu binário efetivamente, no formato de executável Unix.
Assim, linkar estaticamente uma biblioteca ou framework ao seu projeto deixa este executável maior, consequentemente com maior tempo de inicialização do aplicativo em memória. Mas até o iOS 8, esta era a única forma de linkar código de terceiro ao seu projeto
Na imagem abaixo, podemos ver que o linker pega os diversos arquivos compilados e as diversas bibliotecas que você inclui no seu projeto e gera um binário final. Esse binário contém seu código e as bibliotecas compiladas e carrega tudo no heap de memória durante a execução.
Linkagem dinâmica
A segunda forma de linkar código de terceiro ao seu projeto é a dinâmica: nessa, nenhum código ou conteúdo executável é anexado diretamente ao binário principal. O linker apenas adiciona referências ao conteúdo executável da biblioteca dinâmica, como endereços de memória e referência ao arquivo no qual o executável deve buscar o código objeto e conteúdo executável.
Assim, a princípio, carregar o aplicativo em memória é mais rápido, pois a biblioteca é carregada dinamicamente quando um de seus símbolos é requisitado. No iOS, watchOS e tvOS não é possível, mas no macOS você pode distribuir uma nova versão da biblioteca sem ter que distribuir uma nova versão do aplicativo, já que o novo código é buscado/carregado na nova inicialização do aplicativo. Além disso, as bibliotecas dinâmicas possuem ciclo de vida e processo de inicialização e finalização/limpeza de memória próprios.
Mas não é porque o binário final do aplicativo fica menor que podemos sair adicionando diversas bibliotecas ao projeto no iOS: no processo de inicialização, a assinatura do seu aplicativo e de todas as bibliotecas é verificada, o que é um processo também demorado.
Na imagem abaixo podemos ver que, diferente da linkagem estática, aqui apenas a referência às bibliotecas dinâmicas é adicionada ao seu aplicativo, e que as bibliotecas são carregadas, durante a execução, na pilha de memória, e não no heap junto com o aplicativo.
Bibliotecas (libraries) vs Arcabouços (Frameworks)
Temos falado aqui de bibliotecas, mas e os frameworks?
De forma mais técnica, uma biblioteca é um arquivo em formato binário Mach-O, que dependendo da forma de linkagem é carregado na inicialização do aplicativo ou dinamicamente de acordo com a necessidade.
Bibliotecas dinâmicas evitam a repetição/cópia de código entre a aplicação e extensões – como extensões de teclado, Today extensions e da Siri – pois o binário existe em apenas um local. Com a linkagem estática, o conteúdo da biblioteca é copiado dentro de cada extensão.
Ao compilar um ou mais arquivos fontes, cada um gera um arquivo *.o, chamado de arquivo objeto, que contém o código executável daquele arquivo. Uma biblioteca é um recipiente para um conjunto de arquivos objetos. Bibliotecas estáticas usam a extensão .a, gerado ao arquivar um conjunto de arquivos objeto, e bibliotecas dinâmicas possuem a extensão .dylib (aqui vale uma nota, lembrada pelo Ronaldo F. Lima durante o processo de revisão – dylib é usada apenas pelo Darwin, base BSD do macOS; outras vasriações de Unix e o Linux costumam usar a extensão .so, de shared object).
Uma característica do processo de linkagem é que na compilação o linker só pode usar arquivos objeto de uma arquitetura. Por isso existem dois “formatos” de biblioteca estática, chamados de container:
- Arquivos objeto de mesma arquitura em único archive;
- Binário fat Mach-O, criado com comando lipo.
Com o comando lipo é possível pegar diversos arquivos de biblioteca com os mesmo arquivos objetos mas em diferentes arquiteturas, e incluir tudo em um único “binário gordo”, facilitando a distribuição.
Já os frameowrks são análogos a bibliotecas, mas funcionam como um pacote, no qual é possível incluir assets (sons, imagens, vídeos, fontes etc), storyboards, arquivos NIB (a versão compilada do XIB), entre outras coisas. Frameworks dinâmicos podem também ser versionados.
Infelizmente, o Xcode não possui um template para criação de bibliotecas estáticas, mas é possível criá-las via script, sendo, assim, possível distribuir o nosso SDK como um framework, mas para linkagem estática, distribuindo, assim, um conjunto com as imagens e storyboards comuns aos aplicativos. De modo geral, para distribuir os assets é necessário adicionar um target do tiplo Bundle ao seu projeto – mas aqui, uma atenção: mesmo que seu projeto seja para iOS, este deve ser um target “OS X”, e para criar o framework deve ser criada uma estrutura especial de pastas, com uma pasta raiz com a “extensão” .framework. Para mais detalhes de como criar um framework estático, veja esse link.
Voltando à programação normal
Nosso SDK core consiste agora de algumas classes singleton, modelos, categories, protocolos e constantes, além das bibliotecas de segurança mantidas por terceiros. Essas classes podem ser acessadas diretamente dos aplicativos ou direto pelo SDK de UI, já que este também – intencionalmente – depende da biblioteca core.
Como a biblioteca core é garantida de já estar em memória quando a biblioteca de UI é inicializada – e ambas começam durante a inicialização do aplicativo por causa da linkagem estática – o que fazemos é definir um protocolo no SDK core, adotar e implementar esse protocolo na biblioteca de UI.
Vamos ver um exemplo de como isso funciona. Um exemplo de conteúdo de interface que é disparado pelo core são alertas e erros – de conexão ou do backend. Assim, tínhamos no core – agora na biblioteca de UI – classes de view ou view controller para exibir essas telas. Mas o core não sabe mais essas classes – e nem deve ser responsabilidade dele saber (agora temos a flexibilidade de alterar todo o fluxo) – e o que fazemos é declarar um protocolo com um método responsável por exibir a interface. Por exemplo, podemos declarar na biblioteca core o protocolo TPAAlertDelegate como abaixo:
@protocol TPAAlertDelegate + (void)presentInterfaceWithTitle:(NSString *) title message:(NSString *)message; @end
E, por exemplo, na interface da classe de rede – singleton – que inicialmente mandava exibir a view ou view controller, declaramos uma variável que armazenará a referência fraca para a classe que exibirá efetivamente a interface.
@interface TPANetworkManager: NSObject + (instancetype)sharedManager; @property (nonatomic, weak) Class<TPAAlertDelegate>alertInterfaceDelegate; @end
Perceba aqui que o tipo declarado é Class<TPAAlertDelegate> e não id<TPAAlertDelegate> como normalmente fazemos. Já explicamos o porquê.
Agora digamos que a interface a ser exibida é uma view controller chamada aqui de TPAAlertViewController. Para fazer as duas interagirem de forma transparente e sem a interação/configuração do desenvolvedor dos aplicativos – já que esses são SDKs desenvolvidos e mantidos pelo time de SDK, eu gostaria de evitar jogar essa responsabilidade para outro – implementamos o método +load na classe TPAAlertViewController como a seguir.
@implementation TPAAlertViewController + (void)load { [TPANetworkManager sharedManager].alertInterfaceDelegate = self; } @end
Aqui, self está referenciando a classe TPAAlertViewController, e não a uma instância desta, já que esse método é chamado, como dissemos, quando a biblioteca que contém esta classe está sendo carregada em memória – ou seja, na inicialização do aplicativo. Por isso a property alertInterfaceDelegate deve ser declarada como Class<TPAAlertDelegate>. Além disso, como a biblioteca de UI depende da biblioteca core, já temos a classe TPANetworkManager carregada na pilha de memória, e o singleton pode ser carregado/inicializado normalmente e configurado sem intervenção de quem “consome” esses SDKs.
Com tudo configurado podemos, na implementação do método do SDK core no caso de um erro de requisição, chamar o método do delegate.
@implementation TPANetworkManager - (void)someMethodCalledByMainApp { [AFNetworking request... if (error != nil) { [self.alertInterfaceDelegate presentInterfaceWithTitle:@"Erro" message:error.message]; } ]; } @end
Aqui temos um exemplo simples, mas é interessante tratar de alguma forma que self.alertInterfaceDelegate está configurado – se não estiver, é erro do desenvolvedor dos aplicativos, que não importou a biblioteca de UI como dependência. Podemos, então, fazer o tratamento de erro desejado para erro do programador – como gerar uma exceção com uma finalização anormal do app, algo que seja detectado instantaneamente, não algo que vai ser detectado pelo usuário do aplicativo.
Samples, core, UI… How to jandle it?
Ok, antes tínhamos um único workspace contendo três projetos: Sample, Pods usados pelo SDK e o SDK propriamente, mas agora temos três projetos separados, sendo as bibliotecas core e de UI distribuídas via CocoaPods.
Agora imaginemos a seguinte situação: estamos alterando a biblioteca de UI, adicionando uma nova tela que será chamada pelo core, e para isso precisamos alterar este último para fazer a nova chamada da tela sob determinada condição. Devemos, então, fazer a alteração na biblioteca core, gerar uma nova versão, publicar esta, voltar no projeto de UI, efetuar o pod update e seguir com o desenvolvimento da UI, certo? Errado! Isso é trabalhoso demais.
Pods… local pods FTW!
Mantemos os dois repositórios localmente, e no Podfile da biblioteca de UI adicionamos o core como dependência, mas adicionamos o atributo :path apontado para o caminho local.
target 'UI' do ... pod 'SDKCore', :path => 'path/to/sdk-core/' end
Agora não precisamos publicar toda vez que uma alteração é feita na biblioteca core, facilitando muito. Mas ainda tem uma melhoria, proposta pelo artigo CocoaPods: Working With Internal Pods Without Hassle: use um pouco de git submodule!
Um exemplo de como usar isso no projeto da biblioteca de UI seria:
- No projeto UI (core como dependência)
- $ mkdir Vendor
- $ cd Vendor
- $ git submodule add server/path/to/sdk-core.git
- Crie um grupo no projeto UI, ctrl+click, Add Files do UI.xcodeproj
- navegue até Vendor/sdk-core e adicione o projeto sdk-core.xcodeproj
- navegue até Vendor/sdk-core/Pods e adicione o projeto Pods.xcodeproj
E podemos deixar o podfile do projeto de UI como o modelo a seguir:
target 'UI' do ... pod 'SDKCore', :path => './Vendor/sdk-core/' end
Agora podemos executar pod install no projeto UI que ele irá compilar com a biblioteca core, mas no .podspec deixamos o core sendo baixado do servidor. Para quem desenvolve as duas bibliotecas, temos uma única janela e um único local de alteração, com alterações entre projetos facilitadas.
Qual problema é resolvido com a combinação local pods + git submodule? Um resumo seria:
- Situação: 2 projetos independentes
- duas janelas do Xcode
- UI exige alteração do core
- Altera o projeto core
- Gera nova versão
- Sobe para repositório local de pods internos
- pod update sdk-core dentro do projeto UI
- burocrático e demorado
Mas com a dupla git submodule e CocoaPods temos:
- Fácil manutenção
- uma única área de trabalho, dois projetos independentes
- compilado da forma tradicional
- para quem precisa dar manutenção nos dois projetos
- Clonar o projeto UI
- $ git submodule update –init – UI baixa as dependências de submodulos
- Alterar dentro do workspace o projeto
- Fácil distribuição
- Fácil versionamento
- projetos podem ser versionados individualmente para quem vai usar, e não desenvolver
Ao terminar as alterações dois dois projetos, devemos:
- projetos podem ser versionados individualmente para quem vai usar, e não desenvolver
- efetuar o commit da biblioteca core;
- efetuar o commit da biblioteca de UI
O push pode ser feito a qualquer momento. Mas é importante que o commit da biblioteca de UI seja feito após o commit da biblioteca core, pois será feita uma referência ao commit exato do core do momento que é feito o commit do projeto de UI. Isso é válido se você mudar de branch no projeto core, por exemplo.
Agora no projeto Sample basta fazer algo semelhante, porém adicionando o repositório da biblioteca de UI como dependência. E para baixar todos os submodules recursivamente, executar o comando:
$ git submodule update --init --recursive
Que irá baixar o código da biblioteca de UI e da biblioteca core.
A estrutura de diretórios de nossos projetos agora fica similar a esta:
~/Sample └── .git └── .gitsubmodules └── Podfile └── Pods └── Sample.xcodeproj └── Sample.xcworkspace └── Sources └── Vendor └── SDKUI (submodule) └── .git └── .gitsubmodules └── Podfile └── Pods └── SDKUI.podspec └── SDKUI.xcodeproj └── SDKUI.xcworkspace └── Sources └── Vendor └── SDKCore (submodule) └── .git └── Podfile └── Pods └── SDKCore.podspec └── SDKCore.xcodeproj └── SDKCore.xcodeproj └── Sources
E a estrutura dos projetos dentro do Xcode ficam assim:
▼ Sample (projeto) ▶ Source ▶ Products ▶ Frameworks ▶ Pods ▼ Vendor ▼ SDKUI ▶ Source ▶ Products ▶ Frameworks ▶ Pods ▼ Vendor ▼ SDKCore ▶ Source ▶ Products ▶ Frameworks ▶ Pods ▶ Pods (Projeto) ▶ Pods (Projeto) ▶ Pods (Projeto)
Resultados
Após algumas sprints, o resultado foi:
SDK (apenas o core):
- Redução das linhas de código: de 28000* para 13500 (sendo 7000 linhas de testes)
- Cobertura de testes: de 85% para 83%
- enquanto esse processo era feito, outro desenvolvedor do time chegou a 95% de cobertura na principal branch de desenvolvimento;
- Estes 95% parecem ser um “máximo teórico”, pois as bibliotecas de segurança são escritas majoritariamente em C, impedindo/dificultando a injeção de dependência e mocks/stubs.
UI:
- Projeto novo, iniciado com aproximadamente 9500 linhas de código (sendo 5500 linhas de testes);
o storyboard deve ser convertido para arquivo xib ou código-fonte em breve; - Após migração dos testes da versão anterior da biblioteca core, já nasceu com 82% de cobertura de código;
- Pode ser facilmente versionada e distribuída com CocoaPods;
- Times dos apps só precisam adicionar ao Podfile – nenhuma configuração necessária;
- Podemos criar bibliotecas de UI para macOS/watchOS/tvOS – aplicativo só tem que adicionar a biblioteca de UI correta;
Sample:
- Projeto simplificado
- Redução de linhas de código: de 28000* para 2100
Próximos passos
Com a primeira experiência na definição de um núcleo comum e na integração das dependências, podemos agora gerar a nova versão da biblioteca de interface de forma muito mais simples e sem duplicação de código e recursos.
Além disso, podemos partir para a modularização mais fina desse projeto, como a separação melhor da camada de segurança.
Por último, pretendemos expor a solução de forma que os outros times dos aplicativos possam também modularizar as funcionalidades, ganhando assim a simplificação do trabalho do time de integração, mais detalhes de qualidade de código por times e áreas do aplicativo, facilidade na evolução de áreas separadas e maior estabilidade geral.
Referências:
- Overview of Dynamic Libraries
- Static and Dynamic Libraries
- Match-O Programming Topics
- Git submodule tutorial – Cocoapods might not be the solution
- CocoaPods: Working With Internal Pods Without Hassle
- Objective-C Class Loading and Initialization
- Using CocoaPods to Modularize a Big iOS App
- Framework Programming Guide
Ficou alguma dúvida ou tem algo a dizer? Aproveite os campos abaixo.
***
Artigo publicado originalmente em: https://www.concrete.com.br/2017/07/10/modularizacao-projeto-sdk/