Как узнать во время выполнения, работает ли приложение iOS через установку TestFlight Beta

Можно ли во время выполнения обнаружить, что приложение было установлено через TestFlight Beta (отправленное через iTunes Connect), а не через App Store? Вы можете отправить один комплект приложений и сделать его доступным через оба. Есть ли API, который может определить, каким образом он был установлен? Или квитанция содержит информацию, позволяющую это определить?


person combinatorial    schedule 28.09.2014    source источник
comment
Для ясности, вы говорите о новом бета-тестировании TestFlight через iTunes Connect? Или вы имеете в виду, когда загрузили в TestFlight напрямую?   -  person keji    schedule 28.09.2014
comment
Новая бета-версия TestFlight прояснит   -  person combinatorial    schedule 28.09.2014
comment
Похоже - [NSString containsString:] является дополнением к ios8. Если автоматическое тестирование App Store пытается запустить его на ios7, не пойдет. ([receiveURLString rangeOfString: @sandboxReceipt] .location! = NSNotFound) должно помочь.   -  person rgeorge    schedule 29.09.2014
comment
@rgeorge спасибо, это была глупая ошибка!   -  person combinatorial    schedule 29.09.2014
comment
Я собирался спросить об обнаружении на iOS 6, в которой нет appStoreReceiptURL, но похоже, что приложение TestFlight предназначено только для iOS 8; так что - [NSString containsString] может все-таки подойти. Я отложил бета-тестирование магазина приложений из-за этого, но я предполагаю, что некоторые люди могут использовать стратегию гибридного тестирования, с Ad-Hoc для устаревшего тестирования и бета-версии AppStore для общедоступной бета-версии, поэтому rangeOfString по-прежнему выигрывает.   -  person Gordon Dove    schedule 04.11.2014


Ответы (6)


Для приложения, установленного с помощью TestFlight Beta, файл квитанции называется StoreKit\sandboxReceipt, а не обычный StoreKit\receipt. Используя [NSBundle appStoreReceiptURL], вы можете найти sandboxReceipt в конце URL-адреса.

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

Обратите внимание, что sandboxReceipt также является именем файла квитанции при выполнении сборок локально и для сборок, запускаемых в симуляторе.

person combinatorial    schedule 30.09.2014
comment
Действительно ли это хорошая идея, если приложение вообще не использует StoreKit? - person Legoless; 14.10.2014
comment
Я не понимаю, почему это может быть проблемой, в приложении должна быть квитанция. Взгляните на файловую систему приложения и посмотрите, есть ли каталог StoreKit с квитанцией. - person combinatorial; 14.10.2014
comment
Как уже отмечалось, это работает для локального тестирования на устройстве, но не на симуляторе. Я добавил что-то вроде #if TARGET_IPHONE_SIMULATOR isRunningInTestMode = YES; #endif Очевидно, для этого нужен #import ‹TargetConditionals.h› - person Gordon Dove; 01.11.2014
comment
Компактная версия: [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"] (Верно, если запущен распределенный двоичный файл TestFlight) через Supertop / Haddad - person Nick; 02.02.2015
comment
Этот метод нельзя использовать в пакетах расширений, поскольку квитанция существует только для пакета хоста. - person jeeeyul; 19.08.2015
comment
Мое тестирование iOS 8 приводит к StoreKit/sandboxReceipt при установке в качестве отладочной сборки через Xcode на устройстве или симуляторе. Таким образом, это может неточно отличать сборки testflight от всех других сборок. - person pkamb; 27.08.2015
comment
Также кажется, что возвращает YES при установке сборки с использованием Ad Hoc. - person Keller; 05.10.2015
comment
Это безумие. Должен быть способ без кода ... Я имею в виду, что я использую файлы xcconfig для хранения различных конфигураций для разных сред. Необходимость кодировать это разрушает цель этих файлов ... Мне нужно будет добавить каждый ключ env в один файл с разными именами? :( Или есть способ получше? Почему это не широко распространенная проблема? Почему я не нахожу больше жалоб по этому поводу? - person Nuno Gonçalves; 05.05.2016
comment
@ NunoGonçalves, когда вы загружаете сборку в TestFlight, она может сначала находиться в TestFlight, а затем запускаться в AppStore, поэтому свойства времени сборки нельзя изменить, если одна сборка может находиться в двух местах. Однако ничто не мешает вам сделать специальную сборку для TestFlight, которая никогда не запускается в AppStore, и в этом случае ваш подход работает нормально. - person combinatorial; 05.05.2016
comment
@combinatorial спасибо за ваш ответ. Это действительно имеет смысл. Проблема в том, что я всегда должен помнить, какая сборка должна идти в TestFlight, а какая - в App Store, верно? Мне это кажется очень подверженным ошибкам. Я представляю себе отправку сборки для App Store с промежуточными конечными точками. Или, что еще хуже, испытательный полет на серийных. И тестовый полет уведомляет каждого внутреннего тестировщика, как только он обрабатывается. - person Nuno Gonçalves; 05.05.2016
comment
Я получаю квитанцию, а не sandboxReceipt, когда запускаю это на симуляторе в XCode 8. - person nickdnk; 26.11.2016
comment
Работает, как ожидается, в 2020 году, 6 лет спустя. - person fireant; 09.08.2020

На основе комбинаторного ответа я создал следующий вспомогательный класс 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
    }
  }
}

Мы используем эти методы в нашем проекте для предоставления разных идентификаторов отслеживания или строки подключения для каждой среды:

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

OR:

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

ОБНОВЛЕНИЕ 05-02-2016. Предварительным условием для использования макроса препроцессора, такого как #if DEBUG, является установка некоторых настраиваемых флагов компилятора Swift. Дополнительная информация в этом ответе: https://stackoverflow.com/a/24112024/639227

person LorenzoValentijn    schedule 20.11.2015
comment
@Urkman Убедитесь, что вы устанавливаете флаг -D DEBUG. Дополнительную информацию можно найти здесь. - person Caleb; 03.02.2016
comment
Thnx @Caleb, я добавил дополнительные пояснения о предпосылках к ответу. - person LorenzoValentijn; 05.02.2016
comment
Спасибо за ответ, мне он очень помог! Также полезно знать, что с помощью #if targetEnvironment(simulator) вы определяете, работаете ли вы в симуляторе. Итак, у меня есть варианты Simulator / TestFlight / AppStore (которые в моем случае предпочтительнее Debug) :-) - person Jeroen; 03.12.2019

Современная версия Swift, которая учитывает симуляторы (на основе принятого ответа):

private func isSimulatorOrTestFlight() -> Bool {
    guard let path = Bundle.main.appStoreReceiptURL?.path else {
        return false
    }
    return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
}
person Serhii Yakovenko    schedule 16.08.2016
comment
Приятно включить симулятор, но вы можете изменить имя функции, поскольку оно больше не актуально для всех случаев. - person dbn; 29.09.2016
comment
УХ ТЫ! Оно работает! Потрясающие! Возвращает TRUE для TestFlight и FALSE для AppStore для одной и той же сборки (одна сборка построена по одной схеме с одной подготовкой). Идеально! Благодарю вас! - person Argus; 26.09.2018
comment
@dbn, не могли бы вы объяснить, почему это больше не верно для всех случаев? - person Ethan; 04.10.2019
comment
@Ethan, этот ответ был отредактирован после того, как я оставил свой комментарий; раньше имя метода было isTestFlight() - person dbn; 05.10.2019

Я использую расширение Bundle+isProduction в Swift 5.2:

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
}
person Denis Kutlubaev    schedule 04.04.2020

Обновлять

Это больше не работает. Воспользуйтесь другим методом.

Оригинальный ответ

Это тоже работает:

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

Найдено в Определить, загружено ли приложение iOS из Apple Testflight

person Marián Černý    schedule 02.05.2015
comment
Могу ли я с помощью этого метода проверить, находится ли приложение в обзоре или вживую? - person Khushbu Judal; 25.11.2020

Есть один способ, которым я использую его в своих проектах. Вот шаги.

В Xcode перейдите в настройки проекта (проект, а не цель) и добавьте в список конфигурацию «бета»:

введите здесь описание изображения



Затем вам нужно создать новую схему, которая будет запускать проект в «бета» конфигурации. Чтобы создать схему, перейдите сюда:

введите здесь описание изображения



Назовите эту схему как хотите. Вам следует отредактировать настройки для этой схемы. Для этого нажмите здесь:

введите здесь описание изображения



Выберите вкладку "Архив", где вы можете выбрать Build configuration

введите здесь описание изображения



Затем вам нужно добавить ключ Config со значением $(CONFIGURATION) в список свойств информации о проектах следующим образом:

введите здесь описание изображения



Тогда дело в том, что вам нужно в коде, чтобы сделать что-то конкретное для бета-сборки:

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
}
person Klemen    schedule 16.02.2017
comment
Хотя это полезный метод, он не дает ответа на вопрос. Один двоичный файл отправляется в App Store и может быть запущен либо после загрузки через TestFlight, либо позже после одобренного запуска после загрузки из App Store. Вопрос в том, чтобы определить, какая версия работает. - person combinatorial; 16.02.2017
comment
Есть возможность сделать в первую очередь 2 архива. один для testflight один для магазина приложений. - person Klemen; 17.02.2017
comment
Это возможно, но у них должны быть разные номера сборки. А это означает управление двумя сборками вместо одной. - person combinatorial; 17.02.2017
comment
ок, на мой взгляд оно того стоит. Особенно, если вы используете инструменты непрерывной интеграции. - person Klemen; 17.02.2017
comment
@KlemenZagar, ваш подход хорошо известен и хорош, но он не отвечает на вопрос. - person Stanislav Pankevich; 08.05.2017
comment
Это полезный метод. Спасибо, что поделились. - person AechoLiu; 31.05.2018