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

123

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

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

Ответы:

118

Для приложения, установленного через 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это также имя файла квитанции при выполнении сборок локально и для сборок, запускаемых в симуляторе.

комбинаторный
источник
7
Как уже отмечалось, это работает для локального тестирования на устройстве, но не на симуляторе. Я добавил что-то вроде #if TARGET_IPHONE_SIMULATOR isRunningInTestMode = YES; #endif Очевидно, для этого нужен #import <TargetConditionals.h>
Гордон Дав
13
Компактная версия: [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"](Верно, если запущен распределенный двоичный файл TestFlight) через Supertop / Haddad
Ник
2
Этот метод нельзя использовать в пакетах расширений, поскольку квитанция существует только для пакета хоста.
jeeeyul
2
Мои результаты тестирования iOS 8 StoreKit/sandboxReceiptполучаются при установке в качестве отладочной сборки через Xcode на устройстве или симуляторе. Таким образом, это может неточно отличать сборки testflight от всех других сборок.
pkamb
3
Также кажется, что возвращает YES при установке сборки с использованием Ad Hoc.
Келлер
75

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

ИЛИ:

  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

LorenzoValentijn
источник
1
@Urkman Убедитесь, что вы устанавливаете -D DEBUGфлаг. Более подробную информацию можно найти здесь .
Калеб
Thnx @Caleb, я добавил дополнительные пояснения о предпосылках к ответу.
LorenzoValentijn
1
Спасибо за ответ, мне он очень помог! Также полезно знать, что с помощью #if targetEnvironment(simulator)вы определяете, бегаете ли вы в симуляторе. Итак, у меня есть варианты Simulator / TestFlight / AppStore (которые в моем случае предпочтительнее Debug) :-)
JeroenJK
39

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

private func isSimulatorOrTestFlight() -> Bool {
    guard let path = Bundle.main.appStoreReceiptURL?.path else {
        return false
    }
    return path.contains("CoreSimulator") || path.contains("sandboxReceipt")
}
Сергей Яковенко
источник
Приятно включить симулятор, но вы можете изменить имя функции, так как оно больше не верно для всех случаев.
dbn
2
ВОТ ЭТО ДА! Оно работает! Потрясающие! Возвращает TRUE для TestFlight и FALSE для AppStore для одной и той же сборки (одна сборка построена по одной схеме с одной подготовкой). Отлично! Спасибо!
Argus
@dbn, не могли бы вы объяснить, почему это больше не верно для всех случаев?
Итан
1
@Ethan этот ответ был отредактирован после того, как я оставил свой комментарий; раньше имя метода былоisTestFlight()
dbn 04
6

Обновить

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

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

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

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

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

Мариан Черный
источник
2

Я использую расширение 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
}
Денис Кутлубаев
источник
-3

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

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