iOS 中有没有一种方法可以以编程方式检查当前运行的应用程序是否是从 iOS App Store 安装的?这与通过 Xcode、TestFlight 或任何非官方分发源运行的应用程序形成对比。
这是在无法访问应用程序源代码的 SDK 的上下文中。
要明确的是 - 我正在寻找一些签名,可以这么说,给应用程序(可能是苹果),它将在不依赖任何预处理器标志或其他构建配置的情况下,在运行时可供任何应用程序访问。
从 App Store 下载的应用程序都有一个由商店添加的
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-AC82Eu200cu200cB5EA837/iTunesMetadata .plist,不幸的是,无法从应用程序访问它(权限被拒绝)
2015 年 11 月 4 日更新:
检查收据名称似乎会有所帮助。必须注意的是,这个解决方案略有不同:它不返回我们是否正在运行 App Store 应用程序,而是返回我们是否正在运行 beta Testflight 应用程序。根据您的具体情况,这可能有用也可能没用。
最重要的是,这是一个非常脆弱的解决方案,因为收据名称可能随时更改。无论如何,我都会报告它,以防您没有其他选择:
// 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
基于 HockeyKit 类的可能的 Swift 类可能是:
//
// 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
}
}
要点:GitHub - mvarie/WhereAmIRunning.swift
2016 年 12 月 9 日更新:
用户halileohalilei 报告称“这不再适用于iOS10 和Xcode 8。”。我没有验证这一点,但请检查更新的 HockeyKit 源代码(参见函数
bit_currentAppEnvironment
):
来源:GitHub - bitstadium/HockeySDK-iOS - BITHockeyHelper.m
随着时间的推移,上面的类已经被修改,它似乎也可以处理iOS10。
2020 年 10 月 6 日更新:
Hockey 已被弃用/放弃,并由 Microsoft 的 AppCenter SDK 取代。
这是他们的 App Store / Testflight 构建检测类(链接到下面代码的存储库):
MSUtility+Environment.h
:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import <Foundation/Foundation.h>
#import "MSUtility.h"
/*
* Workaround for exporting symbols from category object files.
*/
extern NSString *MSUtilityEnvironmentCategory;
/**
* App environment
*/
typedef NS_ENUM(NSInteger, MSEnvironment) {
/**
* App has been downloaded from the AppStore.
*/
MSEnvironmentAppStore = 0,
/**
* App has been downloaded from TestFlight.
*/
MSEnvironmentTestFlight = 1,
/**
* App has been installed by some other mechanism.
* This could be Ad-Hoc, Enterprise, etc.
*/
MSEnvironmentOther = 99
};
/**
* Utility class that is used throughout the SDK.
* Environment part.
*/
@interface MSUtility (Environment)
/**
* Detect the environment that the app is running in.
*
* @return the MSEnvironment of the app.
*/
+ (MSEnvironment)currentAppEnvironment;
@end
MSUtility+Environment.m
:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#import "MSUtility+Environment.h"
/*
* Workaround for exporting symbols from category object files.
*/
NSString *MSUtilityEnvironmentCategory;
@implementation MSUtility (Environment)
+ (MSEnvironment)currentAppEnvironment {
#if TARGET_OS_SIMULATOR || TARGET_OS_OSX || TARGET_OS_MACCATALYST
return MSEnvironmentOther;
#else
// MobilePovision profiles are a clear indicator for Ad-Hoc distribution.
if ([self hasEmbeddedMobileProvision]) {
return MSEnvironmentOther;
}
/**
* TestFlight is only supported from iOS 8 onwards and as our deployment target is iOS 8, we don't have to do any checks for
* floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1).
*/
if ([self isAppStoreReceiptSandbox]) {
return MSEnvironmentTestFlight;
}
return MSEnvironmentAppStore;
#endif
}
+ (BOOL)hasEmbeddedMobileProvision {
BOOL hasEmbeddedMobileProvision = !![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
return hasEmbeddedMobileProvision;
}
+ (BOOL)isAppStoreReceiptSandbox {
#if TARGET_OS_SIMULATOR
return NO;
#else
if (![NSBundle.mainBundle respondsToSelector:@selector(appStoreReceiptURL)]) {
return NO;
}
NSURL *appStoreReceiptURL = NSBundle.mainBundle.appStoreReceiptURL;
NSString *appStoreReceiptLastComponent = appStoreReceiptURL.lastPathComponent;
BOOL isSandboxReceipt = [appStoreReceiptLastComponent isEqualToString:@"sandboxReceipt"];
return isSandboxReceipt;
#endif
}
@end
来源:GitHub - microsoft/appcenter-sdk-apple - MSUtility+Environment.m
如果您正在谈论自己的应用程序,则可以添加一个状态,如果它是作为商店版本的一部分构建的(例如编译器条件),则返回 true,而在其他情况下返回 false。
如果您正在谈论另一个应用程序,则查询沙箱之外的其他应用程序并不容易或直接(甚至可能不可能)。
由于 @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
}
您可以检查嵌入在主二进制文件的 Mach-O 中的应用程序签名的证书链,看看它是否由预期的中间证书和根证书签名。
同样的技巧也很有用,例如,如果您想查看 Microsoft AppCenter 等服务如何在其测试系统中对您的应用程序进行签名。
我的观察是,当设备连接到 Xcode,然后我们打开 Organizer,切换到设备窗格时,它将列出所有未从 App Store 安装的应用程序。因此,您要做的就是下载 Xcode,然后连接您的设备,转到设备窗格并查看哪些应用程序是从非 App Store 来源安装的。这是最简单的解决方案。