管理具有不同设置和名称的开发、测试和生产构建的最佳方式

Best way to Manage Development, Testing, and Production builds with different settings and name

我有三个 API,具有不同的 API Keys 和一些不同的设置


我可以使用 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

我正在寻找这样的方法

#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

有没有办法在不创建不同目标的情况下创建三个不同名称的构建?


我刚刚根据 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=1ENV_STAGING=1ENV_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