Build-konfigurációk kezelése Xcode-ban
The Missing Manual
for Swift Development
The Guide I Wish I Have When I Started Out
Join 20,000+ Developers Learning About Swift Development
Download Your Free Copy
Egy vadonatúj Xcode projekt két build konfigurációt definiál, Debug és Release. A legtöbb projekt különböző okokból egy vagy több további build-konfigurációt definiál. Ez nem újdonság, és jó gyakorlat a build-konfigurációk használata, hogy a buildet a telepítendő környezet speciális igényeihez igazítsuk.
Ebben az epizódban megmutatom, hogyan lehet biztonságosan kezelni a build-konfigurációra jellemző adatokat, például az API-kulcsokat, hitelesítő adatokat és más érzékeny adatokat. Többféle stratégia létezik a build-konfigurációk kezelésére, de vannak fontos különbségek, amelyeket figyelembe kell venned, különösen a biztonság szempontjából.
Konfiguráció hozzáadása
Tüzeld fel az Xcode-ot, és hozz létre egy új projektet, kiválasztva a Single View App sablont az iOS > Alkalmazás szakaszból.
Nevezd el a projektet Konfigurációknak, állítsd a nyelvet Swiftre, és ellenőrizd, hogy az alján lévő jelölőnégyzetek nincsenek-e bejelölve. Mondja meg az Xcode-nak, hogy hol szeretné tárolni a projektet, és kattintson a Létrehozás gombra.
Nyissa meg a bal oldali Projektnavigátort, és kattintson a projektre a tetején. Válassza ki a projektet a Projekt részben a projekt részleteinek megjelenítéséhez. Egy vadonatúj Xcode projekt két build-konfigurációt határoz meg, a Debug és a Release konfigurációt.
A Debug konfigurációt általában a fejlesztés során használja, míg a Release konfigurációt az App Store vagy a TestFlight buildek létrehozásához. Ez valószínűleg nem újdonság az Ön számára.
Nagyon sok alkalmazás kommunikál egy backenddel, és általános gyakorlat, hogy van egy staging és egy production környezet. A staging környezetet a fejlesztés során használják. Kattintson duplán a Debug konfigurációra, és nevezze át Stagingre.
Adjunk hozzá egy harmadik konfigurációt a termelési környezethez. Kattintsunk a táblázat alján lévő + gombra, válasszuk a Duplicate “Staging” Configuration lehetőséget, és nevezzük el a konfigurációt Production.
Hozzunk létre egy sémát mindegyik konfigurációhoz, hogy megkönnyítsük a környezetek közötti gyors váltást. Jelöljük ki a sémát a tetején, és válasszuk a Sémák kezelése… menüpontot. Válasszuk ki a Konfigurációk nevű sémát, és kattintsunk rá még egyszer. Nevezze át Stagingre.
A séma kiválasztása után kattintson az alsó fogaskerék ikonra, és válassza a Duplikálás lehetőséget. Nevezze el a sémát Termelésnek. Válassza a bal oldalon a Futtatás lehetőséget, és állítsa a Build Configuration-t Production-re.
Ez minden. Most már van egy build konfigurációnk a staging és a production számára. A sémák segítségével gyorsan és egyszerűen válthatunk a build-konfigurációk között.
A felhasználó által meghatározott build-beállítások
Amint korábban említettem, több megoldás is létezik az adott build-konfigurációra jellemző adatok kezelésére. Ebben az epizódban egy olyan megoldást mutatok be, amelyet minden olyan projektben használok, amely némi összetettséggel bír. Mielőtt kiteregetném az általam elképzelt megoldást, szeretnék megmutatni egy másik, a fejlesztők által gyakran használt megoldást.
Válassza ki a projektet a bal oldali Projektnavigátorban. Válassza ki a Konfigurációk célpontot a Célok szakaszból, majd kattintson a tetején lévő Építési beállítások fülre.
Az Építési beállítások fülön a Konfigurációk célpont építési beállításai láthatók. Lehetőség van arra, hogy ezt a listát az Ön által meghatározott építési beállításokkal bővítse. Kattintson a tetején lévő + gombra, és válassza a Felhasználó által definiált beállítás hozzáadása lehetőséget.
Nevezze el a felhasználó által definiált beállítást BASE_URL-nek. Az alap-URL meghatározása gyakori a backenddel interakcióba lépő alkalmazásoknál.
Mi az előnye a felhasználó által definiált beállítás meghatározásának? A felhasználó által definiált beállítás lehetővé teszi, hogy a BASE_URL értékét minden egyes építési konfigurációhoz megadjuk. A felhasználó által definiált beállítás bal oldalán található háromszögre kattintva megjelenik a build-konfigurációk listája.
A Production és Release értékét https://cocoacasts.com-re, a Staging értékét pedig https://staging.cocoacasts.com-re állítsuk.
Amint a neve is mutatja, a build-beállítás a build-folyamat során érhető el. A kódja nem férhet hozzá közvetlenül az Ön által definiált build-beállításokhoz. Ez egy gyakori tévhit. Van azonban megoldás. Nyissa meg az Info.plist fájlt, és adjon hozzá egy új kulcs/érték párt. Állítsa a kulcsot BASE_URL-re, az értéket pedig $(BASE_URL)
.
Mit ér egy kulcs/érték pár hozzáadása a projekt Info.plist-jéhez? A kulcs/érték páros értéke a készítéskor frissül. Az Xcode megvizsgálja az aktuális építési konfiguráció építési beállításait, és beállítja a BASE_URL kulcs értékét a projekt Info.plistjében.
Kipróbáljuk ki. Nyissuk meg az AppDelegate.swift fájlt, és navigáljunk a application(_:didFinishLaunchingWithOptions:)
metódushoz. Hozzá kell férnünk az Info.plist fájl tartalmához. Ez a Bundle
osztály infoDictionary
tulajdonságán keresztül lehetséges. Az a köteg, ami minket érdekel, a main bundle. Ahogy a neve is mutatja, a infoDictionary
tulajdonság egy kulcs/érték párokat tartalmazó szótár, és mi a BASE_URL
kulcs értékéhez férünk hozzá.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Bundle.main.infoDictionary?) return true}
Válasszuk ki a Production sémát a tetején, és futtassuk az alkalmazást a szimulátorban. Ellenőrizzük a kimenetet a konzolon.
Optional(https://staging.cocoacasts.com)
Válasszuk felül a Staging sémát, és futtassuk az alkalmazást a szimulátorban. Ellenőrizze a kimenetet a konzolon. Ez nagyon szép. Ugye?
Optional(https://staging.cocoacasts.com)
Egy szó a biztonságról
A legtöbb fejlesztő nagyon kényelmesnek találja ezt a megoldást. És az is. Van azonban egy probléma. Az App Store-ból letöltött alkalmazásokból könnyű kinyerni az Info.plist fájlt. Mi történik, ha API-kulcsokat, hitelesítő adatokat vagy más érzékeny információkat tárol az Info.plist fájlban? Ez jelentős biztonsági kockázatot jelent, amit el lehet és el is kell kerülnünk.
Mutatnék egy egyszerű megoldást, amely ugyanazt a rugalmasságot, típusbiztonságot és jobb biztonságot kínálja. Hozzon létre egy új Swift fájlt, és nevezze el Configuration.swiftnek.
Definiáljon egy enumot Configuration névvel és egy String
típusú nyers értékkel. Minden egyes build konfigurációhoz definiálunk egy esetet, staging
, production
és release
.
import Foundationenum Configuration: String { // MARK: - Configurations case staging case production case release}
Mielőtt folytatnánk a Configuration enum megvalósítását, frissítenünk kell az Info.plist fájlt. Nyissuk meg az Info.plist fájlt, és adjunk hozzá egy kulcs/érték párt. A kulcs a Configuration, az érték pedig $(CONFIGURATION)
. A CONFIGURATION értéke automatikusan be van állítva számunkra. Nem kell aggódnunk miatta. Ahogy a neve is mutatja, az érték megegyezik annak a build-konfigurációnak a nevével, amellyel a build létrejön.
Nézzük meg újra a Configuration.swift fájlt. Szeretnénk könnyen hozzáférni a build konfigurációjához, a projekt Info.plist fájljában tárolt értékhez. Definiáljunk egy statikus, konstans current
típusú, Configuration
típusú tulajdonságot. Hozzáférünk az Info.plist fájlban a Configuration kulcshoz tárolt értékhez, és egy String
példányra alakítjuk. Végzetes hibát dobunk, ha ez nem sikerül, mert ez soha nem történhet meg.
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") } }()}
Az Info.plist fájlból származó értéket használjuk egy Configuration
példány létrehozásához. Újabb végzetes hibát dobunk, ha az inicializálás sikertelen. Vegyük észre, hogy az Info.plist fájlban tárolt értéket kisbetűvel írjuk. A Configuration
példányt a lezárás visszaadja. Ne felejtsünk el egy pár zárójelet csatolni a lezáráshoz.
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 }()}
Kipróbáljuk ki. Nyissuk meg az AppDelegate.swift állományt, és írjuk ki az aktuális konfigurációt. Válasszuk ki a Staging sémát, futtassuk az alkalmazást, és vizsgáljuk meg a kimenetet a konzolon.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.current) return true}
staging
Konfiguráció bővítése
Valószínűleg tudod, hogy szeretem kihasználni az enumokat névterek létrehozására. Hadd mutassam meg, hogyan javíthatjuk a Configuration
enum jelenlegi implementációját. A legegyszerűbb megoldás egy baseURL
típusú, URL
típusú statikus, számított tulajdonság definiálása. A tulajdonságot statikus, konstans tulajdonságként is definiálhatjuk. Ez egy személyes döntés. Egy switch
utasítással adjuk vissza az alap URL-t minden egyes build-konfigurációhoz.
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")! } }}
Még néhány részletre szeretnék rámutatni. Először is, nem használok default
esetet. Kifejezetten megadom az egyes build-konfigurációkhoz visszaadott értéket. Ez megkönnyíti a problémák felismerését, valamint olvashatóbbá és intuitívabbá teszi a kódot. Másodszor, a felkiáltójelet arra használom, hogy kikényszerítsem a URL
struktúra inicializálója által visszaadott érték kicsomagolását. Ez azon ritka esetek egyike, amikor a felkiáltójelet használom egy érték kicsomagolásának kikényszerítésére. Kényelmes, de ami még fontosabb, hogy az alap URL soha nem lehet egyenlő a nil
.
Az AppDelegate.swift megnyitása és a baseURL
számított tulajdonság értékének kiírása. A sémát a Stagingre állítva futtassuk az alkalmazást, és vizsgáljuk meg a kimenetet a konzolon.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.baseURL) return true}
https://staging.cocoacasts.com
Ez a minta rendelkezik néhány előnnyel az általunk megvalósított első megoldással szemben. Kihasználjuk a Swift típusbiztonságát, és már nem egy opcionális értékkel van dolgunk. Az Xcode automatikus kiegészítését is kihasználhatjuk. Ezek az előnyök finomak, de idővel értékelni fogjuk őket.
Tegyük fel a cseresznyét a tortára azzal, hogy névtereket adunk a keverékhez. Hozzunk létre egy új Swift fájlt, és nevezzük el Configuration+DarkSky.swift.
Készítsünk egy kiterjesztést a Configuration enum számára, és definiáljunk egy DarkSky
nevű enumot a kiterjesztésben. A Dark Sky egy időjárási szolgáltatás, amelyet időről időre használok.
import Foundationextension Configuration { enum DarkSky { }}
A DarkSky
enum egy apiKey
típusú, String
típusú statikus, állandó tulajdonságot definiál. Bekapcsoljuk az aktuális konfigurációt, és minden egyes építési konfigurációhoz más értéket adunk vissza. Ahogy korábban említettem, a tulajdonságot statikus, változó tulajdonságként is deklarálhatjuk. Ennek eldöntése rajtunk múlik.
import Foundationextension Configuration { enum DarkSky { static let apiKey: String = { switch Configuration.current { case .staging: return "123" case .production: return "456" case .release: return "789" } }() }}
Ez a megközelítés több előnnyel is jár. A Dark Sky API konfigurációja szépen névtérbe van helyezve. Ez azt is megkönnyíti, hogy a Dark Sky API konfigurációját egy külön fájlba helyezzük.
Nyissuk meg az AppDelegate.swift fájlt, és írjuk ki a Dark Sky időjárási szolgáltatás API-kulcsát. A sémát Stagingre állítva futtassa az alkalmazást, és vizsgálja meg a kimenetet a konzolon.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { print(Configuration.DarkSky.apiKey) return true}
123
Mi következik?
Ez a minta kényelmet, típusbiztonságot és biztonságot ad a projektekhez. Könnyen elsajátítható, és ha egyszer már megvalósítottuk, egyszerűen bővíthető. Ezt a mintát több okból is szívesen használom. Erősen ajánlom, hogy próbálja ki.