¿Cómo puedo detectar si la aplicación que se está ejecutando se instaló desde la app store?


¿Hay alguna forma en iOS de verificar mediante programación si la aplicación que se está ejecutando actualmente se instaló desde la Tienda de aplicaciones de iOS? Esto contrasta con una aplicación que se ejecutó a través de Xcode, TestFlight o cualquier fuente de distribución no oficial.

Esto es en el contexto de un SDK que no tiene acceso al código fuente de la aplicación.

Para ser claros, estoy buscando alguna firma, por así decirlo, dada a la aplicación (presumiblemente por Apple), que, sin dependencia de ninguna bandera de preprocesador o otras configuraciones de compilación, sean accesibles para cualquier aplicación en tiempo de ejecución.

Author: Carl Veazey, 2013-08-17

5 answers

Las aplicaciones descargadas de la App Store tienen un archivo iTunesMetadata.plist agregado por la tienda:

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

Tal vez desee comprobar si este archivo existe.

Actualización :

En iOS8, el paquete de aplicaciones se ha movido. Según @ silyevsk, el plist está ahora un nivel por encima de [la nueva ruta del paquete principal de la aplicación], en /private/var/mobile/Containers/Bundle/Application/4A74359F-E6CD-44C9-925D-AC82EB5EA837/iTunesMetadata.plist, y por desgracia, esto no puede ser accedido desde la aplicación (permiso denegado)

Actualización 4 de noviembre 2015:

Parece que comprobar el nombre del recibo puede ayudar. Debe tenerse en cuenta que esta solución es ligeramente diferente: no devuelve si estamos ejecutando una aplicación de App Store, sino más bien si estamos ejecutando una aplicación beta Testflight. Esto podría o no ser útil dependiendo de su contexto.

Además, es una solución muy frágil porque el nombre del recibo podría cambiar en cualquier momento. Lo estoy reportando de todos modos, en caso de que no tenga otras opciones:

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

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

Fuente: Detectar si la aplicación iOS se descarga desde Testflight de Apple

Cómo lo hace HockeyKit

Al combinar las diversas comprobaciones, puede adivinar si la aplicación se está ejecutando en un Simulador, en una compilación de Testflight o en una compilación de AppStore.

Aquí hay un segmento de 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;
}

Fuente: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

A la posible clase Swift, basada en la clase de HockeyKit, podría ser:

//
//  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
    }

}

Síntesis: GitHub - mvarie/WhereAmIRunning.swift

Actualización 9 de diciembre 2016:

El usuario halileohalilei informa que "Esto ya no funciona con iOS10 y Xcode 8.". No he verificado esto, pero por favor revise la fuente actualizada de HockeyKit (vea function bit_currentAppEnvironment) en:

Fuente: GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m

Con el tiempo, la clase anterior ha sido modificado y parece manejar iOS10 también.

 26
Author: magma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:30:32

Si está hablando de su propia aplicación, podría agregar un estado que devuelve true si se compila como parte de una versión de tienda (por ejemplo, un compilador condicional) y false en todos los demás casos.

Si estás hablando de otra aplicación, no es fácil o directo (o tal vez ni siquiera posible) consultar otras aplicaciones fuera de tu sandbox.

 4
Author: Michael Dautermann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-16 21:27:20

Dado que el código de @magma ya no funciona IOS11.1, aquí hay una solución un poco larga.

Comprobamos la versión de la aplicación en la app store y la comparamos con la versión del Paquete

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
}

Llamado así

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
}
 1
Author: Ryan Heitner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-23 15:07:27

Mi observación es cuando un dispositivo conectado a Xcode, y luego abrimos Organizador, cambiar al panel Dispositivos que enumerará todas las aplicaciones que no está instalado desde la Tienda de aplicaciones. Entonces, lo que tiene que hacer es descargar Xcode, luego conectar su dispositivo, ir al panel Dispositivos y ver qué aplicaciones están instaladas desde fuentes que no son de la Tienda de aplicaciones. Esta es la solución más simple.

 0
Author: rakeshNS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-04 05:36:21

Puede usar la macro de preprocesador de DEPURACIÓN para determinar si Xcode creó la aplicación o si se creó para la App Store.

BOOL isInAppStore = YES;

#ifdef DEBUG
    isInAppStore = NO;
#endif

Esto debería establecer el BOOL en NO para todos los casos, excepto cuando se descarga desde la App Store.

 -3
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-05 01:20:28