Articles

Správa konfigurací sestavení v Xcode

Zdroje

Stáhněte si zdarma kopii knihy
Chybějící příručka
pro vývoj ve Swiftu

Příručka, kterou bych si přál mít, když jsem začínal

Přidejte se k 20,000+ Vývojáři, kteří se učí o vývoji ve Swiftu

Stáhněte si bezplatnou kopii

Zcela nový projekt Xcode definuje dvě konfigurace sestavení, Debug a Release. Většina projektů definuje z různých důvodů jednu nebo více dalších konfigurací sestavení. To není nic nového a je dobrým zvykem používat konfigurace sestavení k přizpůsobení sestavení specifickým potřebám prostředí, do kterého bude nasazeno.

V tomto díle vám ukážu, jak bezpečně spravovat data, která jsou specifická pro konfiguraci sestavení, jako jsou klíče API, pověření a další citlivá data. Existuje několik strategií správy konfigurací sestavení, ale existují důležité rozdíly, které je třeba zvážit, zejména v souvislosti se zabezpečením.

Přidání konfigurace

Zapněte Xcode a vytvořte nový projekt výběrem šablony Single View App v sekci iOS >Aplikace.

Setting Up the Project

Pojmenujte projekt Configurations, nastavte Language na Swift a ujistěte se, že políčka ve spodní části jsou odškrtnutá. Řekněte aplikaci Xcode, kam chcete projekt uložit, a klikněte na tlačítko Vytvořit.

Setting Up the Project

Otevřete Navigátor projektu vlevo a klikněte na projekt v horní části. V části Project (Projekt) vyberte projekt, aby se zobrazily podrobnosti o projektu. Zcela nový projekt Xcode definuje dvě konfigurace sestavení, Debug a Release.

Build Configurations in Xcode

Konfiguraci Debug běžně používáte během vývoje, zatímco konfigurace Release se používá pro vytváření sestavení App Store nebo TestFlight. To pro vás pravděpodobně není nic nového.

Mnoho aplikací komunikuje s backendem a je běžnou praxí mít prostředí pro staging a produkční prostředí. Prostředí staging se používá při vývoji. Poklepejte na konfiguraci Debug a přejmenujte ji na Staging.

Build Configurations in Xcode

Přidáme třetí konfiguraci pro produkční prostředí. Klikněte na tlačítko + ve spodní části tabulky, vyberte možnost Duplikovat konfiguraci „Staging“ a pojmenujte konfiguraci Production.

Duplicating a Build Configuration

Duplicating a Build Configuration

Vytvoříme schéma pro každou konfiguraci, abychom mohli snadno a rychle přepínat mezi prostředími. Vyberte schéma v horní části a v nabídce zvolte možnost Spravovat schémata…. Vyberte schéma s názvem Konfigurace a klikněte na něj ještě jednou. Přejmenujte jej na Staging.

Update Schemes

Při vybraném schématu klikněte dole na ikonu ozubeného kola a vyberte možnost Duplikovat. Pojmenujte schéma Production (Výroba). Vlevo vyberte možnost Spustit a nastavte Konfiguraci sestavení na hodnotu Production.

Update Schemes

To je vše. Nyní máme konfiguraci sestavení pro staging a production. Díky schématům lze snadno a rychle přepínat mezi konfiguracemi sestavení.

Uživatelem definované nastavení sestavení

Jak jsem již zmínil, existuje několik řešení pro správu dat, která jsou specifická pro konkrétní konfiguraci sestavení. V tomto díle ukážu řešení, které používám v každém projektu, který má určitou složitost. Než vám vyložím řešení, které mám na mysli, rád bych vám ukázal jiné řešení, které vývojáři často používají.

V Navigátoru projektu vlevo vyberte projekt. V části Targets (Cíle) vyberte cíl Configurations (Konfigurace) a v horní části klikněte na kartu Build Settings (Nastavení sestavení).

Target Build Settings

Karta Build Settings (Nastavení sestavení) zobrazuje nastavení sestavení pro cíl Configurations. Tento seznam je možné rozšířit o vámi definovaná nastavení sestavení. Klikněte na tlačítko + v horní části a vyberte možnost Přidat uživatelsky definované nastavení.

Adding a User-Defined Setting

Nazvěte uživatelsky definované nastavení BASE_URL. Definování základní adresy URL je běžné u aplikací, které komunikují s backendem.

Adding a User-Defined Setting

Jaký je přínos definování uživatelsky definovaného nastavení? Uživatelem definované nastavení nám umožňuje nastavit hodnotu pro BASE_URL pro každou konfiguraci sestavení. Kliknutím na trojúhelník vlevo od uživatelsky definovaného nastavení zobrazíte seznam konfigurací sestavení.

Adding a User-Defined Setting

Nastavte hodnotu pro Production a Release na https://cocoacasts.com a hodnotu pro Staging na https://staging.cocoacasts.com.

Adding a User-Defined Setting

Jak název napovídá, nastavení sestavení je dostupné během procesu sestavení. Váš kód nemá přímý přístup k vámi definovanému nastavení sestavení. To je běžná mylná představa. Existuje však řešení. Otevřete soubor Info.plist a přidejte novou dvojici klíč/hodnota. Nastavte klíč na BASE_URL a hodnotu na $(BASE_URL).

Updating Info.plist

Jaký význam má přidání dvojice klíč/hodnota do souboru Info.plist projektu? Hodnota dvojice klíč/hodnota se aktualizuje v době sestavení. Xcode zkontroluje nastavení sestavení pro aktuální konfiguraci sestavení a nastaví hodnotu klíče BASE_URL v souboru Info.plist projektu.

Vyzkoušejme si to. Otevřete soubor AppDelegate.swift a přejděte na metodu application(_:didFinishLaunchingWithOptions:). Potřebujeme získat přístup k obsahu souboru Info.plist. To je možné prostřednictvím vlastnosti infoDictionary třídy Bundle. Svazek, který nás zajímá, je hlavní svazek. Jak název napovídá, vlastnost infoDictionary je slovník dvojic klíč/hodnota a my přistupujeme k hodnotě pro klíč BASE_URL.

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

Vyberte nahoře schéma Production a spusťte aplikaci v simulátoru. Zkontrolujte výstup v konzole.

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

Vyberte nahoře schéma Staging a spusťte aplikaci v simulátoru. Zkontrolujte výstup v konzole. To je docela pěkné. Že?“

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

Slovo o bezpečnosti

Většina vývojářů považuje toto řešení za velmi pohodlné. A také je. Je tu však jeden problém. Soubor Info.plist lze snadno extrahovat z aplikací stažených z App Store. Co se stane, pokud do souboru Info.plist uložíte klíče API, pověření nebo jiné citlivé informace? To přináší značné bezpečnostní riziko, kterému se můžeme a měli bychom vyhnout.

Rád bych vám ukázal jednoduché řešení, které nabízí stejnou flexibilitu, typovou bezpečnost a lepší zabezpečení. Vytvořte nový soubor Swift a pojmenujte jej Configuration.swift.

Creating a Swift File

Creating a Swift File

Definice enumu s názvem Configuration a surovou hodnotou typu String. Pro každou konfiguraci sestavení definujeme případ staging, production a release.

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

Než budeme pokračovat v implementaci enumu Configuration, musíme aktualizovat soubor Info.plist. Otevřete soubor Info.plist a přidejte do něj dvojici klíč/hodnota. Klíčem je Configuration a hodnotou $(CONFIGURATION). Hodnota CONFIGURATION se nám nastaví automaticky. Nemusíme se o ni starat. Jak název napovídá, hodnota se rovná názvu konfigurace sestavení, s níž je sestavení vytvořeno.

Update Info.plist

Přečtěte si Configuration.swift. Chceme mít snadný přístup ke konfiguraci sestavení, tedy k hodnotě uložené v souboru Info.plist projektu. Definujte statickou, konstantní vlastnost current typu Configuration. Získáme přístup k hodnotě, která je uložena v souboru Info.plist pro klíč Configuration, a předáme ji instanci String. Pokud se to nepodaří, vyhodíme fatální chybu, protože by se to nikdy nemělo stát.

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") } }()}

Použijeme hodnotu ze souboru Info.plist k vytvoření instance Configuration. Pokud se inicializace nezdaří, vyhodíme další fatální chybu. Všimněte si, že hodnotu uloženou v souboru Info.plist píšeme malým písmenem. Instance Configuration je vrácena z uzávěrky. Nezapomeňte k uzávěru připojit dvojici závorek.

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 }()}

Vyzkoušíme si to. Otevřete soubor AppDelegate.swift a vypište aktuální konfiguraci. Vyberte schéma Staging, spusťte aplikaci a prohlédněte si výstup v konzoli.

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

Rozšíření konfigurace

Jistě víte, že rád využívám enumy pro vytváření jmenných prostorů. Ukážu vám, jak můžeme vylepšit současnou implementaci výčtu Configuration. Nejjednodušším řešením je definovat statickou, vypočtenou vlastnost baseURL typu URL. Vlastnost můžete také definovat jako statickou, konstantní vlastnost. To už je ale na osobní volbě. Pro vrácení základní adresy URL pro každou konfiguraci sestavení použijeme příkaz switch.

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")! } }}

Rád bych upozornil na několik detailů. Za prvé, nepoužívám příkaz default. Výslovně uvádím hodnotu, která se vrací pro každou konfiguraci sestavení. Díky tomu je snazší odhalit problémy a kód je díky tomu čitelnější a intuitivnější. Za druhé, vykřičník používám k vynucenému rozbalení hodnoty vrácené inicializátorem struktury URL. Toto je jeden z mála případů, kdy používám vykřičník k vynucenému rozbalení hodnoty. Je to pohodlné, ale důležitější je, že základní adresa URL by se nikdy neměla rovnat nil.

Otevřete AppDelegate.swift a vypište hodnotu vypočtené vlastnosti baseURL. Se schématem nastaveným na Staging spusťte aplikaci a zkontrolujte výstup v konzoli.

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

Tento vzor má oproti prvnímu řešení, které jsme implementovali, několik výhod. Využíváme typovou bezpečnost Swiftu a již se nezabýváme nepovinnou hodnotou. Můžeme také využít výhod automatického dokončování v Xcode. Tyto výhody jsou nenápadné, ale časem je oceníte.

Třešničkou na dortu je přidání jmenných prostorů. Vytvořte nový soubor Swift a pojmenujte jej Configuration+DarkSky.swift.

Creating a Swift File

Creating a Swift File

Vytvořte rozšíření pro výčet Configuration a definujte v něm výčet se jménem DarkSky. Dark Sky je meteorologická služba, kterou občas používám.

import Foundationextension Configuration { enum DarkSky { }}

V enumu DarkSky je definována statická, konstantní vlastnost apiKey typu String. Zapínáme aktuální konfiguraci a pro každou konfiguraci sestavení vracíme jinou hodnotu. Jak jsem se již zmínil, vlastnost můžete deklarovat také jako statickou, proměnnou vlastnost. To záleží na vašem rozhodnutí.

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

Tento přístup má několik výhod. Konfigurace pro rozhraní API Dark Sky je pěkně pojmenovaná. Díky tomu je také snadné umístit konfiguraci pro Dark Sky API do samostatného souboru.

Otevřete soubor AppDelegate.swift a vypište klíč API pro meteorologickou službu Dark Sky. Se schématem nastaveným na Staging spusťte aplikaci a zkontrolujte výstup v konzoli.

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

Co dál?

Tento vzor přidává do vašich projektů pohodlí, typovou bezpečnost a zabezpečení. Je snadné si jej osvojit a po jeho implementaci jej lze jednoduše rozšířit. Je to vzor, který rád používám z řady důvodů. Vřele doporučuji jej vyzkoušet.