2013-08-16 4 views
23

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

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

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

+0

Просьба уточнить. Что вы подразумеваете под SDK? Вы строите библиотеку? Вы строите SDK, который выводит приложение à la Titanium? В этом случае вы все равно строите приложение, так что, хотя вам может быть запрещено получать доступ к исходному коду вашего суб-приложения, вы определенно контролируете запущенное приложение. – magma

+2

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

+0

@CarlVeazey Да, это точно есть. – kevlar

ответ

25

приложения, загруженные из App Store есть iTunesMetadata.plist файл добавлен в магазин:

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

Может быть, вы могли бы хотеть, чтобы проверить, существует ли этот файл.

Update:

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

Update Nov 4th 2015:

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

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

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

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

Источник: Detect if iOS App is Downloaded from Apple's 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

U pdate 9 декабря 2016 года:

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

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

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

+0

"' NSHomeDirectory() '" вернет путь к ~ '/ iTunesMetadata.plist', который звучит так, как будто * вне * песочницы приложений. Вы уверены, что это правильный путь для проверки? –

+2

@MichaelDautermann: хороший пункт! Однако в iOS NSHomeDirectory() ведет себя по-разному: «В iOS домашний каталог представляет собой папку для песочницы приложения. В OS X это папка с песочницей приложения или домашний каталог текущего пользователя (если приложение не находится в изолированной области)» – magma

+0

Попробуй это и проверьте, работает оно или нет, спасибо за предложение. – kevlar

4

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

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

+0

Спасибо за ваш быстрый ответ Майкл. Я отредактировал свой вопрос, чтобы указать, что я создаю SDK, поэтому у меня нет прямого доступа к коду приложения. Знаете ли вы об этом? – kevlar

0

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

+1

Я считаю, что Кевлар хочет сделать это программно, то есть изнутри своего кода, а не вручную через Xcode. –

-2

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

BOOL isInAppStore = YES; 

#ifdef DEBUG 
    isInAppStore = NO; 
#endif 

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

+1

Разве это не просто настройка для каждого приложения в зависимости от того, является ли это отладочной или релизной сборкой? Я делаю выпускные сборки все время без макроса DEBUG. – kevlar

+0

Правильно, но вы можете использовать его, чтобы определить, было ли приложение загружено из App Store. Это то, что мы делаем в нашей статической библиотеке SDK, чтобы определить, следует ли регистрироваться на консоли или нет. Я не уверен, однако, почему вам нужны эти знания, доступные из любого приложения на телефоне? Что вы пытаетесь сделать? – Mark

+1

Подождите, это невозможно. Если разработчик использует наш выпуск SDK, а затем сам запускает тестовую сборку, наш выпуск SDK будет считать загрузку приложения. – kevlar

0

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

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

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 
} 

Called как этот

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 
}