Articles

Gerenciando Configurações de Construção em Xcode

Recursos

Baixe sua cópia gratuita de
O Manual Ausente
para Desenvolvimento Rápido

O Guia que Gostaria de ter Quando Comecei

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Um novo projecto Xcode define duas configurações de compilação, Debug e Release. A maioria dos projetos define uma ou mais configurações de compilação adicionais por vários motivos. Isso não é novo e é uma boa prática usar configurações de build para adaptar um build às necessidades específicas do ambiente para o qual ele será implantado.

Neste episódio, eu mostro como gerenciar com segurança dados específicos para uma configuração de build, como chaves de API, credenciais e outros dados sensíveis. Existem várias estratégias para gerenciar configurações de build, mas existem diferenças importantes que você precisa considerar, especialmente no contexto da segurança.

Adicionando uma Configuração

Código X e crie um novo projeto escolhendo o modelo Single View App do iOS > Seção de Aplicações.

Setting Up the Project

Denomine as Configurações do projeto, defina Language para Swift, e certifique-se de que as caixas de seleção na parte inferior estejam desmarcadas. Diga ao Xcode onde você gostaria de armazenar o projeto e clique em Create.

Setting Up the Project

Abra o Project Navigator à esquerda e clique no projeto na parte superior. Selecione o projeto na seção Project (Projeto) para mostrar os detalhes do projeto. Um novíssimo projeto Xcode define duas configurações de build, Debug e Release.

Build Configurations in XcodeBuild Configurations in Xcode

Você normalmente usa a configuração Debug durante o desenvolvimento enquanto a configuração Release é usada para criar builds App Store ou TestFlight. Isto provavelmente não é novidade para você.

Muitas aplicações comunicam com um backend e é uma prática comum ter um ambiente de encenação e produção. O ambiente de encenação é usado durante o desenvolvimento. Clique duas vezes na configuração de Debug e renomeie-a para Staging.

Build Configurations in Xcode

Saiba adicionar uma terceira configuração para o ambiente de produção. Clique no botão + na parte inferior da tabela, escolha Duplicar Configuração “Staging” e nomeie a configuração Produção.

Duplicating a Build Configuration

Duplicating a Build Configuration

Criemos um esquema para cada configuração para facilitar a troca rápida entre ambientes. Seleccione o esquema no topo e escolha Manage Schemes… a partir do menu. Selecione o esquema chamado Configurações e clique nele mais uma vez. Renomeie-o para Staging.

Update Schemes

Com o esquema selecionado, clique no ícone da engrenagem na parte inferior e escolha Duplicate. Nomeie o esquema Produção. Selecione Run à esquerda e defina Build Configuration como Production.

Update Schemes

É isso aí. Agora temos uma configuração de build para a encenação e produção. Os esquemas fazem com que seja rápido e fácil alternar entre configurações de build.

Configurações de Build definidas pelo usuário

Como mencionei anteriormente, existem várias soluções para gerenciar dados específicos de uma configuração de build em particular. Neste episódio, eu mostro uma solução que uso em qualquer projeto que tenha alguma complexidade. Antes de apresentar a solução que tenho em mente, gostaria de mostrar outra solução que é frequentemente utilizada pelos desenvolvedores.

Selecionar o projeto no Project Navigator à esquerda. Selecione o alvo Configurações na seção Alvos e clique na aba Configurações de Compilação no topo.

Target Build Settings

A aba Configurações de Compilação mostra as configurações de compilação para o alvo Configurações. É possível expandir esta lista com as Configurações de Compilação que você definir. Clique no botão + na parte superior e escolha Add User-Defined Setting.

Adding a User-Defined Setting

Nome a configuração definida pelo usuário BASE_URL. Definir uma URL base é comum para aplicações que interagem com um backend.

Adding a User-Defined Setting

Qual é a vantagem de definir uma configuração definida pelo usuário? A configuração definida pelo usuário nos permite definir um valor para BASE_URL para cada configuração de build. Clique no triângulo à esquerda da configuração definida pelo usuário para mostrar a lista de configurações de build.

Adding a User-Defined Setting

Configurar o valor para Produção e Release para https://cocoacasts.com e o valor para Staging para https://staging.cocoacasts.com.

Adding a User-Defined Setting

Como o nome implica, uma configuração de build está disponível durante o processo de build. Seu código não pode acessar diretamente as configurações de compilação que você definir. Este é um equívoco comum. Há uma solução, no entanto. Abra Info.plist e adicione um novo par chave/valor. Defina a chave para BASE_URL e o valor para $(BASE_URL).

Updating Info.plist

Qual é o valor de adicionar um par chave/valor ao Info.plist do projeto? O valor do par chave/valor é atualizado no momento da construção. Xcode inspeciona as configurações de construção para a configuração de construção atual e define o valor da chave BASE_URL no Info.plist.

Vamos experimentá-lo. Abra AppDelegate.swift e navegue para o método application(_:didFinishLaunchingWithOptions:). Precisamos acessar o conteúdo do arquivo Info.plist. Isto é possível através da propriedade infoDictionary da classe Bundle. O pacote em que estamos interessados é o pacote principal. Como o nome implica, a propriedade infoDictionary é um dicionário de pares chave/valor e acessamos o valor para a BASE_URL key.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}

Selecionar o esquema de produção no topo e executar a aplicação no simulador. Inspeccione a saída no console.

Optional(https://staging.cocoacasts.com)

Selecione o esquema de produção na parte superior e execute a aplicação no simulador. Inspeccione a saída na consola. Isso é muito bom. Certo?

Optional(https://staging.cocoacasts.com)

Uma palavra sobre segurança

Os desenvolvedores acham esta solução muito conveniente. E é. Há um problema, no entanto. É fácil extrair o arquivo Info.plist de aplicativos baixados da App Store. O que acontece se você armazenar chaves de API, credenciais ou outras informações confidenciais no arquivo Info.plist? Isto introduz um risco de segurança significativo que podemos e devemos evitar.

I’d like to show you a simple solution that offers the same flexibility, type safety, and improved security. Crie um novo arquivo Swift e nomee-o Configuration.swift.

Creating a Swift File

Creating a Swift File

Definir um enum com o nome Configuration e um valor bruto do tipo String. Definimos um caso para cada configuração de build, staging, production, e release.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}

Antes de continuarmos com a implementação do Enum de Configuração, precisamos atualizar o arquivo Info.plist. Abra o Info.plist e adicione um par chave/valor. A chave é Configuração e o valor é $(CONFIGURATION). O valor de CONFIGURAÇÃO é automaticamente definido para nós. Nós não precisamos de nos preocupar com isso. Como o nome implica, o valor é igual ao nome da configuração do build com o qual o build é criado.

Update Info.plist

Revisit Configuration.swift. Nós queremos acesso fácil à configuração do build, o valor armazenado no arquivo Info.plist do projeto. Defina uma propriedade estática, constante, current, do tipo Configuration. Nós acessamos o valor que é armazenado no arquivo Info.plist para a configuração da chave e o lançamos a uma instância String. Lançamos um erro fatal se isso falhar porque isso nunca deveria acontecer.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } }()}

Usamos o valor do arquivo Info.plist para criar uma instância Configuration. Nós lançamos outro erro fatal se a inicialização falhar. Note que nós rebaixamos o valor armazenado na lima do Info.plist. A instância Configuration é devolvida a partir do fechamento. Não se esqueça de anexar um par de parênteses ao fechamento.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }()}

Vamos experimentar. Abra AppDelegate.swift e imprima a configuração atual. Selecione o Staging scheme, execute a aplicação, e inspecione a saída no console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}
staging

Configuração de extensão

Você provavelmente sabe que eu gosto de aproveitar os enums para criar namespaces. Deixe-me mostrar-lhe como podemos melhorar a implementação actual do Configuration enum. A solução mais simples é definir uma propriedade estática, computada, baseURL, do tipo URL. Você também pode definir a propriedade como uma propriedade estática, constante. Esta é uma escolha pessoal. Utilizamos uma declaração switch para retornar a URL base para cada configuração de build.

import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release // MARK: - Current Configuration static let current: Configuration = { guard let rawValue = Bundle.main.infoDictionary? as? String else { fatalError("No Configuration Found") } guard let configuration = Configuration(rawValue: rawValue.lowercased()) else { fatalError("Invalid Configuration") } return configuration }() // MARK: - Base URL static var baseURL: URL { switch current { case .staging: return URL(string: "https://staging.cocoacasts.com")! case .production, .release: return URL(string: "https://cocoacasts.com")! } }}

Existem vários detalhes que gostaria de salientar. Primeiro, eu não uso um caso default. Estou explícito sobre o valor que é retornado para cada configuração de build. Isto facilita a detecção de problemas e torna o código mais legível e intuitivo. Segundo, eu uso o ponto de exclamação para forçar o desembrulhamento do valor retornado pelo inicializador da estrutura URL. Este é um dos raros cenários em que eu utilizo o ponto de exclamação para forçar o desembrulhamento de um valor. É conveniente, mas, mais importante, a URL base nunca deve ser igual a nil.

Open AppDelegate.swift e imprimir o valor da propriedade baseURL computed. Com o esquema definido para o Staging, execute a aplicação e inspecione a saída no console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com

Este padrão tem algumas vantagens sobre a primeira solução que implementamos. Nós aproveitamos a segurança do tipo Swift e não estamos mais lidando com um valor opcional. Também podemos nos beneficiar do auto-completamento do Xcode. Essas vantagens são sutis, mas você passa a apreciá-las com o tempo.

Vamos colocar a cereja no bolo adicionando namespaces à mistura. Crie um novo arquivo Swift e nomeie-o como Configuration+DarkSky.swift.

Creating a Swift File

Creating a Swift File

Criar uma extensão para o enum de Configuração e definir um enum com nome DarkSky na extensão. Dark Sky é um serviço meteorológico que eu uso de tempos em tempos.

import Foundationextension Configuration { enum DarkSky { }}

> O enum definido como DarkSky define uma propriedade estática, constante, apiKey, do tipo String. Nós ligamos a configuração atual e retornamos um valor diferente para cada configuração de construção. Como mencionei anteriormente, você também pode declarar a propriedade como uma propriedade estática, variável. Isso cabe a você decidir.

import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}

Existem várias vantagens para esta abordagem. A configuração para o Dark Sky API é bem espaçada. Isso também facilita colocar a configuração para a API do Céu Escuro em um arquivo separado.

AppDelegate.swift e imprimir a chave da API para o serviço de tempo do Céu Escuro. Com o esquema definido para Staging, execute o aplicativo e inspecione a saída no console.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123

What’s Next?

Este padrão adiciona conveniência, tipo de segurança, e segurança aos seus projetos. É fácil de adotar e, uma vez implementado, é simples de estender. É um padrão que eu gosto de usar por uma série de razões. Eu recomendo vivamente que experimente.