【问题标题】:How to tell at runtime whether an iOS app is running through a TestFlight Beta install如何在运行时判断 iOS 应用程序是否正在通过 TestFlight Beta 安装运行
【发布时间】:2014-11-22 18:17:57
【问题描述】:

是否可以在运行时检测到应用程序已通过 TestFlight Beta(通过 iTunes Connect 提交)而不是 App Store 安装?您可以提交单个应用程序包并通过两者提供。是否有可以检测其安装方式的 API?或者收据是否包含可以确定这一点的信息?

【问题讨论】:

  • 为了清楚起见,您是在谈论通过 iTunes Connect 进行的新 TestFlight beta 测试?还是说你是直接上传到TestFlight的时候?
  • 新的TestFlight beta,将澄清
  • 看起来 -[NSString containsString:] 是 ios8 添加的。如果 App Store 自动测试尝试在 ios7 上运行它,那就不行了。 ([receiptURLString rangeOfString:@"sandboxReceipt"].location != NSNotFound) 应该可以解决问题。
  • @rgeorge 谢谢,这是一个愚蠢的错误!
  • 我想问一下在没有 appStoreReceiptURL 的 iOS 6 上检测,但似乎 TestFlight 应用程序仅适用于 iOS 8;所以 -[NSString containsString] 毕竟可能没问题。因此,我已暂停应用商店 beta 测试,但我猜有些人可能会使用混合测试策略,即 Ad-Hoc 用于旧版测试,而 AppStore beta 用于公共测试版,因此 rangeOfString 仍然胜出。

标签: ios testflight


【解决方案1】:

对于通过 TestFlight Beta 安装的应用程序,收据文件被命名为 StoreKit\sandboxReceipt,而不是通常的 StoreKit\receipt。使用[NSBundle appStoreReceiptURL],您可以在 URL 的末尾查找 sandboxReceipt。

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSString *receiptURLString = [receiptURL path];
BOOL isRunningTestFlightBeta =  ([receiptURLString rangeOfString:@"sandboxReceipt"].location != NSNotFound);

请注意,sandboxReceipt 也是在本地运行构建和在模拟器中运行构建时收据文件的名称。

Swift 版本:

let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"

【讨论】:

  • 如前所述,这适用于设备上的本地测试,但不适用于模拟器。我添加了类似#if TARGET_IPHONE_SIMULATOR isRunningInTestMode = YES; #endif 显然,这需要#import
  • 精简版:[[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"](如果运行 TestFlight 分布式二进制文件则为真)通过Supertop/Haddad
  • 此方法不能用于扩展包,因为收据只存在于主机包中。
  • 在使用 Ad Hoc 分发安装构建时似乎也返回 YES。
  • 在 6 年后的 2020 年按预期工作。
【解决方案2】:

基于combinatorial's answer,我创建了以下 SWIFT 帮助程序类。使用此类,您可以确定它是调试、测试飞行还是应用商店构建。

enum AppConfiguration {
  case Debug
  case TestFlight
  case AppStore
}

struct Config {
  // This is private because the use of 'appConfiguration' is preferred.
  private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
  
  // This can be used to add debug statements.
  static var isDebug: Bool {
    #if DEBUG
      return true
    #else
      return false
    #endif
  }

  static var appConfiguration: AppConfiguration {
    if isDebug {
      return .Debug
    } else if isTestFlight {
      return .TestFlight
    } else {
      return .AppStore
    }
  }
}

我们在项目中使用这些方法为每个环境提供不同的跟踪 ID连接字符串

  func getURL(path: String) -> String {    
    switch (Config.appConfiguration) {
    case .Debug:
      return host + "://" + debugBaseUrl + path
    default:
      return host + "://" + baseUrl + path
    }
  }

或者:

  static var trackingKey: String {
    switch (Config.appConfiguration) {
    case .Debug:
      return debugKey
    case .TestFlight:
      return testflightKey
    default:
      return appstoreKey
    }
  }

2016 年 5 月 2 日更新: 使用 #if DEBUG 之类的预处理器宏的先决条件是设置一些 Swift 编译器自定义标志。此答案中的更多信息:https://stackoverflow.com/a/24112024/639227

【讨论】:

  • @Urkman 确保您设置了-D DEBUG 标志。更多信息可以在here找到。
  • Thnx @Caleb,我对答案的先决条件添加了更多解释。
  • 感谢您的回答,我发现它很有帮助!也很高兴知道,使用#if targetEnvironment(simulator) 可以确定您是否在模拟器中运行。所以我有 Simulator/TestFlight/AppStore 选项(在我的情况下,这比 Debug 更受欢迎):-)
【解决方案3】:

现代 Swift 版本,用于模拟器(基于接受的答案):

private func isSimulatorOrTestFlight() -> Bool {
    guard let path = Bundle.main.appStoreReceiptURL?.path else {
        return false
    }
    return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
}

【讨论】:

  • 很高兴包含模拟器,但您可能需要更改函数名称,因为它不再适用于所有情况。
  • 哇!有用!惊人的!对于同一构建(在一个方案中构建的一个构建,具有一个配置),对于 TestFlight 返回 TRUE,对于 AppStore 返回 FALSE。完美的!谢谢!
  • @dbn 你能详细说明为什么这不再适用于所有情况吗?
  • @Ethan 这个答案是在我发表评论后编辑的;方法名称以前是isTestFlight()
【解决方案4】:

我在 Swift 5.2 上使用扩展名 Bundle+isProduction

import Foundation

extension Bundle {
    var isProduction: Bool {
        #if DEBUG
            return false
        #else
            guard let path = self.appStoreReceiptURL?.path else {
                return true
            }
            return !path.contains("sandboxReceipt")
        #endif
    }
}

然后:

if Bundle.main.isProduction {
    // do something
}

【讨论】:

    【解决方案5】:

    更新

    这不再起作用了。使用其他方法。

    原答案

    这也有效:

    if NSBundle.mainBundle().pathForResource("embedded", ofType: "mobileprovision") != nil {
        // TestFlight
    } else {
        // App Store (and Apple reviewers too)
    }
    

    发现于Detect if iOS App is Downloaded from Apple's Testflight

    【讨论】:

    • 可以用这个方法查看应用是在review还是live吗?
    【解决方案6】:

    我有一种方法可以将它用于我的项目。步骤如下。

    在 Xcode 中,转到项目设置(项目,而不是目标)并将“beta”配置添加到列表中:



    然后你需要创建一个新的方案,它将在“beta”配置中运行项目。要创建方案,请转到此处:



    将此方案命名为您想要的任何名称。您应该编辑此方案的设置。为此,请点按此处:



    选择存档选项卡,您可以在其中选择Build configuration



    然后您需要在项目信息属性列表中添加一个值为$(CONFIGURATION) 的键Config,如下所示:



    然后,您需要在代码中执行特定于 beta 构建的操作,这就是问题所在:

    let config = Bundle.main.object(forInfoDictionaryKey: "Config") as! String
    if config == "Debug" {
      // app running in debug configuration
    }
    else if config == "Release" {
      // app running in release configuration
    }
    else if config == "Beta" {
      // app running in beta configuration
    }
    

    【讨论】:

    • 虽然这是一种有用的技术,但它并不能回答问题。单个二进制文件被提交到 App Store,可以通过 TestFlight 下载运行,也可以在从 App Store 下载后批准运行后运行。问题是关于检测哪个版本正在运行。
    • 是否可以选择首先制作 2 个存档。一个用于测试,一个用于应用商店。
    • 这是可能的,但它们必须具有不同的内部版本号。这意味着管理两个构建而不是一个。
    • 好的,在我看来这是值得的。特别是如果您使用持续集成工具。
    • @KlemenZagar,您的方法是众所周知的好方法,但它没有回答问题。
    猜你喜欢
    • 1970-01-01
    • 2011-11-14
    • 2012-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    • 1970-01-01
    • 2010-10-19
    相关资源
    最近更新 更多