管理具有不同设置和名称的开发、测试和生产构建的最佳方式
Best way to Manage Development, Testing, and Production builds with different settings and name
我有三个 API
,具有不同的 API Keys
和一些不同的设置
用于开发或内部测试构建 - iOS App Store
之外的开发分发
Host
- devapi.project-name.com
API Key
- development_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
But first problem is Enterprise distribution (for client testing) and iOS
App Store distribution (production) build, for Enterprise and App
Store distribution every time need to change code
对于企业分发
#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
或其他?
Second Problem
有没有办法在不创建不同目标的情况下创建三个不同名称的构建?
ProductName
- 适用于 App Store
ProductName-Dev
- 用于内部开发构建
ProductName-Stg
- 用于客户端测试(企业)build
我刚刚根据 iamnichols
提供的解决方案创建了演示项目和完整的视觉指南
调试版本和发布版本之间的区别在于,一个版本被存档并导出,而另一个版本是 运行 在本地通过 Xcode 在调试器中导出。您可能会发现有时您也想在调试器中 运行 生产或暂存构建,但是通过 #ifdef DEBUG
拆分内容,您可能会 运行 遇到问题。
这是我所做的简化版本:
创建个人配置
在项目(非目标)设置中,创建(从原始文件复制)以下配置:
- Debug_Dev
- Debug_Staging
- Debug_Prod
- Release_Dev
- Release_Staging
- Release_Prod
请注意,如果您使用 Cocoapods,则需要将配置设置回 none,删除项目中 Pods 文件夹的内容(不是 Pods project) and re-运行 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 Display Name 的值设置为 $(MYAPP_DISPLAY_NAME)
.
一种更简单、不太复杂的解决方案是为每个配置使用不同的头文件,并且只#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"
详情
- Xcode 版本 10.2.1 (10E1001),Swift 5
解决方案
1) Create (or duplicate) Targets
或
我的样本:
我复制了现有目标并重命名了它们。我的目标名称:
- App-Production
- App-Staging
- App-Development
2) Organise and rename info plists
我把所有的plist放在一个文件夹里:info.plists
3) Rename build schemes
4) Check build schemes params
按下 编辑 按钮
检查您的构建方案是否连接到正确的目标。
我的样本:
App-Development 构建方案(查看下图的左上角)连接到 App-Development 目标(目标位于下图的中心)。
- 在App-Development构建方案中选择的目标是App-Development
- 在App-Staging构建方案中选择的目标是App-Staging
- 在App-Production构建方案中选择的目标是App-Production
同时检查可执行应用程序。
我的样本:
- 在App-Development构建方案中可执行应用是App-Development.app
- 在App-Staging构建方案中可执行应用是App-Staging.app
- 在App-Production构建方案中可执行应用是App-Production.app
5) Add values to the info plists
我的样本:
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)")
当您运行您的构建方案之一时,您将拥有不同的值。
Select build scheme
Run
我有三个 API
,具有不同的 API Keys
和一些不同的设置
用于开发或内部测试构建 - iOS App Store
之外的开发分发Host
- devapi.project-name.comAPI Key
- development_keyFLEX
[1] - 启用
用于客户端测试构建 - iOS App Store
之外的企业分发Host
- stgapi.project-name.comAPI Key
- enterprise_keyFLEX
- 启用
用于生产构建 - 在 iOS App Store
中分发Host
- API.project-name.comAPI key
- app_store_keyFLEX
- 禁用
我可以使用 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
But first problem is Enterprise distribution (for client testing) and iOS App Store distribution (production) build, for Enterprise and App Store distribution every time need to change code
对于企业分发
#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
或其他?
Second Problem
有没有办法在不创建不同目标的情况下创建三个不同名称的构建?
ProductName
- 适用于 App StoreProductName-Dev
- 用于内部开发构建ProductName-Stg
- 用于客户端测试(企业)build
我刚刚根据 iamnichols
提供的解决方案创建了演示项目和完整的视觉指南调试版本和发布版本之间的区别在于,一个版本被存档并导出,而另一个版本是 运行 在本地通过 Xcode 在调试器中导出。您可能会发现有时您也想在调试器中 运行 生产或暂存构建,但是通过 #ifdef DEBUG
拆分内容,您可能会 运行 遇到问题。
这是我所做的简化版本:
创建个人配置
在项目(非目标)设置中,创建(从原始文件复制)以下配置:
- Debug_Dev
- Debug_Staging
- Debug_Prod
- Release_Dev
- Release_Staging
- Release_Prod
请注意,如果您使用 Cocoapods,则需要将配置设置回 none,删除项目中 Pods 文件夹的内容(不是 Pods project) and re-运行 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 Display Name 的值设置为 $(MYAPP_DISPLAY_NAME)
.
一种更简单、不太复杂的解决方案是为每个配置使用不同的头文件,并且只#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"
详情
- Xcode 版本 10.2.1 (10E1001),Swift 5
解决方案
1) Create (or duplicate) Targets
或
我的样本:
我复制了现有目标并重命名了它们。我的目标名称:
- App-Production
- App-Staging
- App-Development
2) Organise and rename info plists
我把所有的plist放在一个文件夹里:info.plists
3) Rename build schemes
4) Check build schemes params
按下 编辑 按钮
检查您的构建方案是否连接到正确的目标。
我的样本:
App-Development 构建方案(查看下图的左上角)连接到 App-Development 目标(目标位于下图的中心)。
- 在App-Development构建方案中选择的目标是App-Development
- 在App-Staging构建方案中选择的目标是App-Staging
- 在App-Production构建方案中选择的目标是App-Production
同时检查可执行应用程序。
我的样本:
- 在App-Development构建方案中可执行应用是App-Development.app
- 在App-Staging构建方案中可执行应用是App-Staging.app
- 在App-Production构建方案中可执行应用是App-Production.app
5) Add values to the info plists
我的样本:
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)")
当您运行您的构建方案之一时,您将拥有不同的值。
Select build scheme
Run