Лучший способ управлять сборками разработки, тестирования и производства с различными настройками и именем
У меня есть три API
с разными API Keys
и несколько разных настроек
-
Для разработки или сборки внутреннего тестирования - Распространение разработки вне iOS App Store
-
Host
- devapi.project-name.com -
API Key
- API Key
разработки -
FLEX
[ 1 ] - Включить
-
Для клиентского тестирования сборки - корпоративное распространение за пределами iOS App Store
-
Host
- stgapi.project-name.com -
API Key
- enterprise_key -
FLEX
- Включить
-
Для производственной сборки - распространение в iOS App Store
-
Host
- API.project-name.com -
API key
- app_store_key -
FLEX
- отключить
Я могу управлять двумя настройками с помощью DEBUG
#if DEBUG
#define API_BASE_URL @"http://devapi.project-name.com/api/v1"
#define API_KEY @"development_key"
#else
#define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
#define API_KEY @"enterprise_key"
#endif
// In AppDelegate.m
#if DEBUG
[[FLEXManager sharedManager] showExplorer];
#endif
Но первая проблема - это дистрибутив Enterprise (для тестирования клиентов) и дистрибутив (сборка) сборки iOS App Store, для дистрибутива Enterprise и App Store каждый раз нужно менять код
-
Для корпоративного распространения
#if DEBUG
//debug setting
#else
//enterprise setting
#define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
#define API_KEY @"enterprise_key"
#endif
-
Для распространения в App Store
#if DEBUG
//debug setting
#else
//app store setting
#define API_BASE_URL @"http://api.project-name.com/api/v1"
#define API_KEY @"app_store_key"
#endif
Я ищу способ что-то вроде этого
#ifdef DEVELOPMENT
#define API_BASE_URL @"http://devapi.project-name.com/api/v1"
#define API_KEY @"development_key"
#elif ENTERPRISE
#define API_BASE_URL @"http://stgapi.project-name.com/api/v1"
#define API_KEY @"enterprise_key"
#elif APP_STORE
#define API_BASE_URL @"http://api.project-name.com/api/v1"
#define API_KEY @"app_store_key"
#endif
Или любой другой?
Вторая проблема
Есть ли способ создать три сборки с разными именами без создания другой цели?
-
ProductName
- для магазина приложений -
ProductName-Dev
- для внутренней разработки build -
ProductName-Stg
- для сборки клиентского тестирования (Enterprise)
Я только что создал демонстрационный проект и полное визуальное руководство на основе решения от iamnichols
Ответы
Ответ 1
Разница между отладкой и сборкой выпуска заключается в том, что он архивируется и экспортируется, а другой выполняется локально через Xcode в отладчике. Возможно, вы обнаружите, что иногда хотите запустить производственную или промежуточную сборку в отладчике, но, разделив содержимое на #ifdef DEBUG
, вы, вероятно, столкнетесь с проблемами.
Это упрощенная версия того, что я делаю:
Создать индивидуальные конфигурации
В настройках проекта (не целевых) создайте (дублируйте из оригиналов) следующие конфигурации:
- Debug_Dev
- Debug_Staging
- Debug_Prod
- Release_Dev
- Release_Staging
- Release_Prod
Обратите внимание, что если вы используете Cocoapods, вам нужно будет установить конфигурации обратно в none, удалить содержимое папки Pods в вашем проекте (Не проект Pods) и повторно запустить pod install
.
Создайте схему для каждой среды
Вместо того, чтобы просто иметь схему MyApp, создайте следующее (дублируйте оригинал):
- MyApp_Dev
- MyApp_Staging
- MyApp_Prod
В каждой схеме используйте соответствующие конфигурации Debug_ * и Release_ *, где это необходимо.
Добавить макрос препроцессора для определения сред
Добавьте дополнительный макрос препроцессора, чтобы определить, с какой средой вы строите.
В настройках сборки проекта щелкните значок + и добавьте пользовательский параметр сборки и назовите его как-то вроде MYAPP_ENVIRONMENT
. Затем для каждой группы различных групп добавьте другой макрос препроцессора к каждому из них. я ENV_DEV=1
, ENV_STAGING=1
и ENV_PROD=1
.
Затем в макросах препроцессора c (опять же на уровне проекта, а не на целевом уровне) добавьте этот новый параметр MYAPP_ENVIRONMENT, используя $(MYAPP_ENVIRONMENT)
.
Таким образом, вы можете определить, с какой средой вы строите против:
#ifdef ENV_DEV
NSString * const MyAppAPIBaseURL = @"https://api-dev.myapp.com/";
#elif ENV_SAGING
NSString * const MyAppAPIBaseURL = @"https://api-staging.myapp.com/";
#elif ENV_PROD
NSString * const MyAppAPIBaseURL = @"https://api.myapp.com/";
#endif
Скорее всего, это займет много времени, но дайте мне знать, как вы продвигаетесь.
Затем вы можете создавать различные пользовательские настройки сборки, чтобы делать разные вещи, например изменять отображаемое имя вашего приложения.
Вы могли бы сделать это, создав, например, новый параметр под названием MYAPP_DISPLAY_NAME
, установите правильное имя для каждой конфигурации, а затем в info.plist
установите значение отображаемого имени Bundle на $(MYAPP_DISPLAY_NAME)
.
Ответ 2
Более простое и менее сложное решение будет использовать разные файлы заголовков для каждой конфигурации, а #importing только один из них. Это не автоматическое, но довольно простое:
// You only need to switch the following lines when passing from qa 2 production and back:
#import "Mode_QA.h"
//#import "Mode_Production.h"
Ответ 3
подробности
- Версия Xcode 10.2.1 (10E1001), Swift 5
Решение
1) Создание (или дублирование) целей
![enter image description here]()
ИЛИ ЖЕ
![enter image description here]()
Мой образец:
Я продублировал существующие цели и переименовал их. Имена моих целей:
- Приложение-производственное
- App-Подмости
- App-развития
2) Организация и переименование информационных списков
Я поместил все списки в одну папку: info.plists
![enter image description here]()
3) Переименовать схемы сборки
![enter image description here]()
![enter image description here]()
4) Проверьте схемы сборки параметров
Нажмите на кнопку Изменить
![enter image description here]()
Убедитесь, что ваша схема сборки подключена к правильным целям.
Мой образец:
Схема сборки разработки приложений (посмотрите на левый верхний угол изображения ниже), подключенная к цели разработки приложений (цели расположены в центре изображения ниже).
- в схеме сборки App-Development выбранной целью является App-Development
- в схеме сборки App-Staging выбранной целью является App-Staging
- в схеме сборки App-Production выбранной целью является App-Production
![enter image description here]()
Также проверьте исполняемое приложение.
Мой образец:
- В схеме сборки App-Development исполняемым приложением является App-Development.app
- В схеме сборки App-Staging исполняемым приложением является App-Staging.app
- В схеме сборки App-Production исполняемым приложением является App-Production.app
![enter image description here]()
5) Добавить значения в информационные списки
![enter image description here]()
Мой образец:
Info-Production.plist
<key>LSEnvironment</key>
<dict>
<key>Environment</key>
<string>Production</string>
<key>Host</key>
<string>https://production.host.com</string>
<key>AppID</key>
<integer>1</integer>
<key>AdvertisementEnabled</key>
<true/>
</dict>
Info-Development.plist
<key>LSEnvironment</key>
<dict>
<key>Environment</key>
<string>Development</string>
<key>Host</key>
<string>https://development.host.com</string>
<key>AppID</key>
<integer>2</integer>
<key>AdvertisementEnabled</key>
<false/>
</dict>
Info-Staging.plist
<key>LSEnvironment</key>
<dict>
<key>Environment</key>
<string>Staging</string>
<key>Host</key>
<string>https://staging.host.com</string>
<key>AppID</key>
<integer>3</integer>
<key>AdvertisementEnabled</key>
<false/>
</dict>
Environment.swift
import Foundation
// MARK: - Environment main class
class Environment {
class Value { private init(){} }
class Enums { private init(){} }
}
extension Environment.Value {
static var all: [String: Any] {
return Bundle.main.infoDictionary?["LSEnvironment"] as? [String: Any] ?? [:]
}
}
extension Environment.Value {
private enum Keys: String {
case environment = "Environment"
case host = "Host"
case appID = "AppID"
case advertisementEnabled = "AdvertisementEnabled"
}
private static func get<T>(value key: Keys, type: T.Type) -> T? {
return all[key.rawValue] as? T
}
}
// MARK: - Environment type value
extension Environment.Enums {
enum EnvironmentType: String {
case production = "Production"
case staging = "Staging"
case development = "Development"
}
}
extension Environment.Value {
static var type: Environment.Enums.EnvironmentType {
let environment = get(value: .environment, type: String.self)!
return Environment.Enums.EnvironmentType(rawValue: environment)!
}
}
// MARK: - Host (sample with string)
extension Environment.Value {
static var host: String { return get(value: .host, type: String.self)! }
}
// MARK: - App ID (sample with number)
extension Environment.Value {
static var appID: Int { return get(value: .appID, type: Int.self)! }
}
// MARK: - Advertisement Enabled (sample with bool)
extension Environment.Value {
static var advertisementEnabled: Bool { return get(value: .advertisementEnabled, type: Bool.self)! }
}
использование
print("All values: \(Environment.Value.all)")
switch Environment.Value.type {
case .development: print("Environment: dev")
case .staging: print("Environment: stage")
case .production: print("Environment: prod")
}
print("Host: \(Environment.Value.host)")
print("App ID: \(Environment.Value.appID)")
print("Advertisement Enabled: \(Environment.Value.advertisementEnabled)")
![enter image description here]()
Когда вы запустите одну из ваших схем сборки, у вас будут разные значения.
Выберите схему сборки
![enter image description here]()
Бежать
![enter image description here]()