Как я могу определить, было ли установлено текущее приложение из магазина приложений?

Есть ли способ в iOS для программной проверки, было ли установлено текущее приложение из магазина приложений iOS? Это контрастирует с приложением, которое запускалось через Xcode, TestFlight или любой не официальный источник распространения.

Это в контексте SDK, который не имеет доступа к исходному коду приложения.

Чтобы быть ясным - я ищу некоторую подпись, если можно так выразиться, приложение (предположительно, Apple), которое будет независимо от любых препроцессорных флагов или других конфигураций сборки быть доступным для любого приложения во время выполнения.

Ответ 1

Приложения, загруженные из магазина приложений, имеют файл iTunesMetadata.plist, добавленный в хранилище:

NSString *file=[NSHomeDirectory() stringByAppendingPathComponent:@"iTunesMetadata.plist"];
if ([[NSFileManager defaultManager] fileExistsAtPath:file]) {
    // probably a store app
}

Возможно, вы захотите проверить, существует ли этот файл.

Обновление

В iOS8 пакет приложений был перемещен. Согласно @silyevsk, plist теперь находится на одном уровне выше [путь основного пула приложений], в /private/var/mobile/Containers/Bundle/Application/ 4A74359F-E6CD-44C9-925D-AC82E B5EA837/iTunesMetadata. plist, и, к сожалению, к нему нельзя получить доступ из приложения (разрешение отклонено)

Обновление 4 ноября 2015:

Похоже, что проверка имени квитанции может помочь. Следует отметить, что это решение немного отличается: оно не возвращает, работает ли приложение App Store, а работает ли приложение для бета-тестирования. Это может быть или не быть полезным в зависимости от вашего контекста.

Кроме того, это очень хрупкое решение, потому что имя получателя может измениться в любое время. Я сообщаю об этом в любом случае, если у вас нет других возможностей:

// Objective-C
BOOL isRunningTestFlightBeta = [[[[NSBundle mainBundle] appStoreReceiptURL] lastPathComponent] isEqualToString:@"sandboxReceipt"];

// Swift
let isRunningTestFlightBeta = NSBundle.mainBundle().appStoreReceiptURL?.lastPathComponent=="sandboxReceipt"

Источник: Обнаруживает, загружается ли приложение iOS из Apple Testflight

Как HockeyKit делает это

Объединив различные проверки, вы можете угадать, работает ли приложение в симуляторе, в сборке Testflight или в сборке AppStore.

Здесь отрезок от HockeyKit:

BOOL bit_isAppStoreReceiptSandbox(void) {
#if TARGET_OS_SIMULATOR
  return NO;
#else
  NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
  NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;

  BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];
  return isSandboxReceipt;
#endif
}

BOOL bit_hasEmbeddedMobileProvision(void) {
  BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
  return hasEmbeddedMobileProvision;
}

BOOL bit_isRunningInTestFlightEnvironment(void) {
#if TARGET_OS_SIMULATOR
  return NO;
#else
  if (bit_isAppStoreReceiptSandbox() && !bit_hasEmbeddedMobileProvision()) {
    return YES;
  }
  return NO;
#endif
}

BOOL bit_isRunningInAppStoreEnvironment(void) {
#if TARGET_OS_SIMULATOR
  return NO;
#else
  if (bit_isAppStoreReceiptSandbox() || bit_hasEmbeddedMobileProvision()) {
    return NO;
  }
  return YES;
#endif
}

BOOL bit_isRunningInAppExtension(void) {
  static BOOL isRunningInAppExtension = NO;
  static dispatch_once_t checkAppExtension;

  dispatch_once(&checkAppExtension, ^{
    isRunningInAppExtension = ([[[NSBundle mainBundle] executablePath] rangeOfString:@".appex/"].location != NSNotFound);
  });

  return isRunningInAppExtension;
}

Источник: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

Возможный класс Swift, основанный на классе HockeyKit, может быть:

//
//  WhereAmIRunning.swift
//  https://gist.github.com/mvarie/63455babc2d0480858da
//
//  ### Detects whether we're running in a Simulator, TestFlight Beta or App Store build ###
//
//  Based on https://github.com/bitstadium/HockeySDK-iOS/blob/develop/Classes/BITHockeyHelper.m
//  Inspired by https://stackoverflow.com/questions/18282326/how-can-i-detect-if-the-currently-running-app-was-installed-from-the-app-store
//  Created by marcantonio on 04/11/15.
//

import Foundation

class WhereAmIRunning {

    // MARK: Public

    func isRunningInTestFlightEnvironment() -> Bool{
        if isSimulator() {
            return false
        } else {
            if isAppStoreReceiptSandbox() && !hasEmbeddedMobileProvision() {
                return true
            } else {
                return false
            }
        }
    }

    func isRunningInAppStoreEnvironment() -> Bool {
        if isSimulator(){
            return false
        } else {
            if isAppStoreReceiptSandbox() || hasEmbeddedMobileProvision() {
                return false
            } else {
                return true
            }
        }
    }

    // MARK: Private

    private func hasEmbeddedMobileProvision() -> Bool{
        if let _ = NSBundle.mainBundle().pathForResource("embedded", ofType: "mobileprovision") {
            return true
        }
        return false
    }

    private func isAppStoreReceiptSandbox() -> Bool {
        if isSimulator() {
            return false
        } else {
            if let appStoreReceiptURL = NSBundle.mainBundle().appStoreReceiptURL,
                let appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent
                where appStoreReceiptLastComponent == "sandboxReceipt" {
                    return true
            }
            return false
        }
    }

    private func isSimulator() -> Bool {
        #if arch(i386) || arch(x86_64)
            return true
            #else
            return false
        #endif
    }

}

Gist: GitHub - mvarie/WhereAmIRunning.swift

Обновление 9 декабря 2016 года:

Пользователь halileohalilei сообщает, что "это больше не работает с iOS10 и Xcode 8.". Я не проверял это, но, пожалуйста, проверьте обновленный источник HockeyKit (см. Функцию bit_currentAppEnvironment) по адресу:

Источник: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

Со временем этот класс был изменен и, похоже, также обрабатывает iOS10.

Ответ 2

Если вы говорите о своем собственном приложении, вы можете добавить состояние, которое возвращает true, если оно было создано как часть версии хранилища (например, условного компилятора) и false в каждом другом случае.

Если вы говорите о другом приложении, вам нелегко или просто (или, возможно, даже не возможно) запросить другие приложения за пределами вашей песочницы.

Ответ 3

Мое наблюдение - когда устройство подключено к Xcode, а затем мы открываем Organizer, переключитесь на панель "Устройства", в нем будут перечислены все Приложения, которые не установлены из App Store. Итак, что вам нужно сделать, это загрузить Xcode, затем подключить свое устройство, перейти на панель "Устройства" и посмотреть, какие приложения все установлены из источников, отличных от App Store. Это самое простое решение.

Ответ 4

Так как код @magma больше не работает IOS11.1 Вот немного долгое решение.

Мы проверяем версию приложения в магазине приложений и сравниваем ее с версией в пакете

static func isAppStoreVersion(completion: @escaping (Bool?, Error?) -> Void) throws -> URLSessionDataTask {
    guard let info = Bundle.main.infoDictionary,
      let currentVersion = info["CFBundleShortVersionString"] as? String,
      let identifier = info["CFBundleIdentifier"] as? String else {
        throw VersionError.invalidBundleInfo
    }
    let urlString = "https://itunes.apple.com/gb/lookup?bundleId=\(identifier)"
    guard let url = URL(string:urlString) else { throw VersionError.invalidBundleInfo }
    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
      do {
        if let error = error { throw error }
        guard let data = data else { throw VersionError.invalidResponse }
        let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String: Any]
        guard let result = (json?["results"] as? [Any])?.first as? [String: Any], let appStoreVersion = result["version"] as? String else {
          throw VersionError.invalidResponse
        }
        completion(appStoreVersion == currentVersion, nil)
      } catch {
        completion(nil, error)
      }
    }
    task.resume()
    return task
}

Вызывается как

DispatchQueue.global(qos: .background).async {

    _ = try? VersionManager.isAppStoreVersion { (appStoreVersion, error) in
      if let error = error {
        print(error)
      } else if let appStoreVersion = appStoreVersion, appStoreVersion == true {
         // app store stuf
      } else {
        // other stuff

      }
    }
}

enum VersionError: Error {
    case invalidResponse, invalidBundleInfo
}

Ответ 5

Вы можете использовать макрос препроцессора DEBUG, чтобы определить, было ли построено Xcode приложение или было ли оно создано для App Store.

BOOL isInAppStore = YES;

#ifdef DEBUG
    isInAppStore = NO;
#endif

Это должно установить BOOL NO для каждого случая, кроме случаев, когда он загружен из App Store.