iOS:使用不同设置和名称管理开发,测试和生产构建的最佳方式

时间:2016-02-23 07:37:28

标签: ios objective-c project-management

我有三个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
  

但第一个问题是企业分发(用于客户端测试)和iOS   适用于企业和应用程序的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 - 适用于App Store
  • ProductName-Dev - 内部开发构建
  • ProductName-Stg - 用于客户端测试(企业)构建

我刚刚根据iamnichols提供的解决方案创建了演示项目和完整的视觉指南

Best way to manage Development, Testing and Production iOS builds with different settings

https://github.com/vineetchoudhary/BuildManagement

3 个答案:

答案 0 :(得分:13)

调试和发布版本之间的区别在于,一个存档并导出但另一个在调试器中通过Xcode本地运行。您可能会发现您有时希望在调试器中运行生产或暂存构建,但是通过#ifdef DEBUG拆分东西,您可能会遇到问题。

这是我所做的简化版本:

创建个人配置

在项目(非目标)设置中,创建(从原件复制)以下配置:

  • Debug_Dev
  • Debug_Staging
  • Debug_Prod
  • Release_Dev
  • Release_Staging
  • Release_Prod

请注意,如果您使用Cocoapods,则需要将配置设置为none,删除项目中Pods文件夹的内容( Not the Pods project )并重新运行{{ 1}}。

为每个环境创建一个方案

不是仅仅拥有MyApp方案,而是创建以下内容(复制原始内容):

  • MyApp_Dev
  • MyApp_Staging
  • MyApp_Prod

在每个方案中,在适当的地方使用关联的Debug_ *和Release_ *配置。

添加预处理器宏以识别环境

添加一个额外的预处理器宏,以确定您正在构建的环境。

在项目构建设置中,单击+并添加用户定义的构建设置,并将其称为pod install。然后,对于每个不同的环境组,为每个环境添加不同的预处理器宏。即MYAPP_ENVIRONMENTENV_DEV=1ENV_STAGING=1

然后,在c预处理器宏中(再次在项目级别而不是目标级别)使用ENV_PROD=1添加此新的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中将Bundle Display Name的值设置为{{1} }。

答案 1 :(得分:0)

更简单且不太复杂的解决方案是为每个配置使用不同的头文件,并且#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"

答案 2 :(得分:0)

详细信息

  • Xcode版本10.2.1(10E1001),Swift 5

解决方案

  

1)创建(或重复)目标

enter image description here

OR

enter image description here

我的样品:

我复制了现有目标并将其重命名。我的目标名称:

  • 应用程序制作
  • 应用程序登台
  • 应用开发
  

2)组织并重命名信息访问者

我将所有plists放在一个文件夹中: 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