如何在OS X或iOS中运行时确定操作系统版本(不使用Gestalt)?

79

从OS X 10.8 Mountain Lion开始,位于CarbonCore/OSUtils.h中的Gestalt()函数已被弃用。

我经常使用这个函数来测试运行时的OS X操作系统版本(请参见下面的示例)。

在Cocoa应用程序中,还可以使用哪些其他API来检查OS X操作系统版本?

int main() {
    SInt32 versMaj, versMin, versBugFix;
    Gestalt(gestaltSystemVersionMajor, &versMaj);
    Gestalt(gestaltSystemVersionMinor, &versMin);
    Gestalt(gestaltSystemVersionBugFix, &versBugFix);

    printf("OS X Version: %d.%d.%d\n", versMaj, versMin, versBugFix);
}

1
这个问题在这里有一些讨论:https://dev59.com/h2XWa4cB1Zd3GeqPIweV#11056915 - Monolo
Gestalt APIs非常古老(实际上是在Carbon之前)并且大部分已经被弃用。虽然它们仍在维护和工作,但在如今使用它们并不是一个好主意,甚至连运行时版本也不推荐使用。 - Motti Shneor
16个回答

77
在OS X 10.10(和iOS 8.0)上,您可以使用[[NSProcessInfo processInfo] operatingSystemVersion],它返回一个NSOperatingSystemVersion结构体,定义如下:
typedef struct {
    NSInteger majorVersion;
    NSInteger minorVersion;
    NSInteger patchVersion;
} NSOperatingSystemVersion;

NSProcessInfo 中还有一个方法可以为您执行比较:

- (BOOL)isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion)version

注意,尽管文档显示在OS X 10.10及更高版本中可以使用,但operatingSystemVersionisOperatingSystemAtLeastVersion:在OS X 10.9(可能为10.9.2)上也存在并且按预期工作。这意味着您不能测试NSProcessInfo是否响应这些选择器来检查您是否正在运行OS X 10.9或10.10。
在iOS上,这些方法实际上只能在iOS 8.0及以上版本中使用。

2
您还可以使用以下Objective-C类别(BSD许可证)在旧操作系统版本上使用这些新方法:https://github.com/petroules/CocoaBackports/blob/master/CocoaBackports/NSProcessInfo%2BPECocoaBackports.m 它应该适用于OS X 10.5和iOS 2.0,包括MRC、ARC或GC以及旧的SDK。 - Jake Petroules
1
从iOS 8 beta 4开始,当在iOS模拟器中运行时,operatingSystemVersion方法将返回正确的版本。 - 0xced
无法工作的 DP7。没有这个选择器(operatingSystemVersion)。 - rozochkin
1
如何从 C++ 代码中调用 [[NSProcessInfo processInfo] operatingSystemVersion]? - Vlad
3
如果我已经知道我正在使用10.10版本的操作系统,我就不需要检查了! - David
显示剩余4条评论

27

在命令行上:

$ sysctl kern.osrelease
kern.osrelease: 12.0.0
$ sysctl kern.osversion
kern.osversion: 12A269

通过编程实现:

#include <errno.h>
#include <sys/sysctl.h>

char str[256];
size_t size = sizeof(str);
int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);

将 Darwin 版本转换为对应的 OS X 发布版本:

17.x.x. macOS 10.13.x High Sierra
16.x.x  macOS 10.12.x Sierra
15.x.x  OS X  10.11.x El Capitan
14.x.x  OS X  10.10.x Yosemite
13.x.x  OS X  10.9.x  Mavericks
12.x.x  OS X  10.8.x  Mountain Lion
11.x.x  OS X  10.7.x  Lion
10.x.x  OS X  10.6.x  Snow Leopard
 9.x.x  OS X  10.5.x  Leopard
 8.x.x  OS X  10.4.x  Tiger
 7.x.x  OS X  10.3.x  Panther
 6.x.x  OS X  10.2.x  Jaguar
 5.x    OS X  10.1.x  Puma

一个获取和测试版本的示例:

#include <string.h>
#include <stdio.h>
#include <sys/sysctl.h>

/* kernel version as major minor component*/
struct kern {
    short int version[3];
};

/* return the kernel version */
void GetKernelVersion(struct kern *k) {
   static short int version_[3] = {0};
   if (!version_[0]) {
      // just in case it fails someday
      version_[0] = version_[1] = version_[2] = -1;
      char str[256] = {0};
      size_t size = sizeof(str);
      int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
      if (ret == 0) sscanf(str, "%hd.%hd.%hd", &version_[0], &version_[1], &version_[2]);
    }
    memcpy(k->version, version_, sizeof(version_));
}

/* compare os version with a specific one
0 is equal
negative value if the installed version is less
positive value if the installed version is more
*/
int CompareKernelVersion(short int major, short int minor, short int component) {
    struct kern k;
    GetKernelVersion(&k);
    if ( k.version[0] !=  major) return major - k.version[0];
    if ( k.version[1] !=  minor) return minor - k.version[1];
    if ( k.version[2] !=  component) return component - k.version[2];
    return 0;
}

int main() {
   struct kern kern;
   GetKernelVersion(&kern);
   printf("%hd %hd %hd\n", kern.version[0], kern.version[1], kern.version[2]);

   printf("up: %d %d eq %d %d low %d %d\n",
        CompareKernelVersion(17, 0, 0), CompareKernelVersion(16, 3, 0),
        CompareKernelVersion(17, 3, 0), CompareKernelVersion(17,3,0),
        CompareKernelVersion(17,5,0), CompareKernelVersion(18,3,0));


}

在我的机器上(macOS High Sierra 10.13.2),测试结果如下:

17 3 0
up: -3 -1 eq 0 0 low 2 1

1
请注意,这些结果与uname(在Carl Norum的答案中讨论)获得的结果相同。它们是内核版本号,有点对应于操作系统发布版,但并不明显。 - Rob Napier
4
永远不要使用这种方法来确定 OS X 或 iOS 的版本。虽然你现在可能能够通过算术方式确定一个 OS X 版本与另一个版本的区别,但这种比较未来可能无法保证有效。以一个具体的例子来说,某个 iOS 版本已经跳过了一个 Darwin 版本;OS X 也可能出现相同的情况。 - Jake Petroules

26

你可以使用 NSAppKitVersionNumber 值来检查不同版本的 AppKit,尽管它们并不完全对应于操作系统版本

if (NSAppKitVersionNumber <= NSAppKitVersionNumber10_7_2) {
    NSLog (@"We are not running on Mountain Lion");
}

4
这也在官方文档中得到了证实(在“运行时版本检查”一节下方):https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html - codingFriend1

19

有一个Cocoa API。您可以使用类NSProcessInfo从中获取OS X版本字符串。

获取操作系统版本字符串的代码如下:

NSString * operatingSystemVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString];

NSLog(@"operatingSystemVersionString => %@" , operatingSystemVersionString);

// ===>> 版本 10.8.2 (Build 12C2034) 的结果值

没有被弃用。


10.10添加了一种以结构化方式获取它的方法(几乎字面意思),但只有在目标为10.10 SDK时才能使用。这在目前情况下相当于失败了。您仍然可以暂时使用Gestalt(),但它已被弃用并可能随时消失。 - Siobhán
这可能很不错。它没有被弃用,并且在10.2+ sdk上工作...不幸的是,苹果文档中说:“操作系统版本字符串是人类可读的、本地化的,并适合显示给用户。该字符串不适合解析。”https://developer.apple.com/reference/foundation/nsprocessinfo/1408730-operatingsystemversionstring?language=objc - BuvinJ
1
@Vikas Bansal 的回答可以说更好。 - BuvinJ
因为它需要解析一个用户可呈现的文本,而这个文本可能会以意想不到的方式在未来发生变化,所以被踩了!它对于向用户展示很好,但对于问题所涉及的任务——根据操作系统版本做出决策的代码编写来说并不适用。 - Thomas Tempelmann

12

如果您只需要检查支持的最低版本,则还可以使用kCFCoreFoundationVersionNumber。它的优势是可以向后兼容到10.1,并且可以在C、C++和Objective-C中完成。

例如,要检查是否为10.10或更高版本:

#include <CoreFoundation/CoreFoundation.h>
if (floor(kCFCoreFoundationVersionNumber) > kCFCoreFoundationVersionNumber10_9) {
    printf("On 10.10 or greater.");
}

你需要与CoreFoundation(或Foundation)框架进行链接。

在Swift中,它的工作方式完全相同。以下是另一个示例:

import Foundation
if floor(kCFCoreFoundationVersionNumber) > kCFCoreFoundationVersionNumber10_8 {
    println("On 10.9 or greater.")
} else if floor(kCFCoreFoundationVersionNumber) > kCFCoreFoundationVersionNumber10_9 {
    println("On 10.10 or greater.")
}

谢谢,这很好,而且正是我在寻找的——不需要Appkit(链接Obj-C代码和运行时),也不需要系统调用、文件读取等等——只需由操作系统维护的简单常量。不过有一件事——为了使这个常量可用,应该包含哪个头文件? - Motti Shneor
@MottiShneor 我添加了必要的 #include 和链接信息。 - kainjow
谢谢!现在完美了 :) 我认为有人(嗯...我?)应该总结所有选项(不是太少,很抱歉),并解释哪种情况下最好。我必须说我个人最喜欢CoreFoundation。然而 - 如果你正在编写一个类Unix守护进程,甚至没有链接CoreFoundation...那么你该怎么办?退回到sysctl()吗? - Motti Shneor
@MottiShneor 为什么你不会在OS X上链接到CF?只需要一个特定于平台的项目设置即可。即使是使用C代码,也可以通过使用Foundation和Objective-C API进行包装来完成。 - kainjow
这些 kCFCoreFoundationVersionNumber10_XXX 常量是提供给比 10.10.3 更高版本的吗?这是我在 XCode 8.0 的 CFBase.h 头文件中找到的最大常量。 - Simon Warta
原来在MacOSX10.12.sdk中,常量可用至kCFCoreFoundationVersionNumber10_11。尝试使用grep kCFCoreFoundationVersionNumber10_ -R /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk查找。只需确保您使用来自SDK的CoreFoundation头文件,而不是系统的即可。 - Simon Warta

11

你可以使用NSOperatingSystemVersion轻松获取操作系统的主版本号、次版本号和补丁版本号。

NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];

NSString* major = [NSString stringWithFormat:@"%d", version.majorVersion];


NSString* minor = [NSString stringWithFormat:@"%d", version.minorVersion];


NSString* patch = [NSString stringWithFormat:@"%d", version.patchVersion];

2
仅支持OS X 10.10及以上版本。 - bleater
1
如果您可以接受@bleater指出的情况,最好的答案是直到10.10才能得到。 - BuvinJ
在构建时或运行应用程序的机器上需要OS X 10.10吗? - Simon Warta
因为需要解析用户可呈现的文本,而该文本未来可能以意想不到的方式发生变化,因此被点踩! - Thomas Tempelmann

9

Gestalt() 是一个纯 C API。接受的答案建议使用 NSProcessInfo,但这需要 Objective-C 代码,如果出于某种原因需要纯 C,则无法使用。同样适用于 NSAppKitVersionNumber,自 10.13.4 以后,它也不再被 Apple 更新。在 C 代码中使用常量 kCFCoreFoundationVersionNumber 是可能的,但这个常量自 10.11.4 以来就没有再更新过了。使用 sysctl 读取 kern.osrelease 在 C 中是可能的,但需要一个映射表或依赖当前的内核版本方案,因此对于未来版本不可靠,因为内核版本方案可能会更改,而映射表无法知道未来的版本。

从 Apple 官方和推荐的方式是读取 /System/Library/CoreServices/SystemVersion.plist,因为这也是 Gestalt() 获取版本号的位置,这也是 NSProcessInfo() 获取版本号的位置,这也是命令行工具 sw_vers 获取版本号的位置,甚至是新的 @available(macOS 10.x, ...) 编译器特性获取版本号的位置(我深入研究了系统才找到这一点)。我想即使 Swift 的 #available(macOS 10.x, *) 也会从那里获取版本号。

只是没有一个简单的 C 调用可以从这里读取版本号,因此我编写了以下纯 C 代码,它只需要 CoreFoundation 框架本身是一个纯 C 框架:

static bool versionOK;
static unsigned versions[3];
static dispatch_once_t onceToken;

static
void initMacOSVersion ( void * unused ) {
    // `Gestalt()` actually gets the system version from this file.
    // Even `if (@available(macOS 10.x, *))` gets the version from there.
    CFURLRef url = CFURLCreateWithFileSystemPath(
        NULL, CFSTR("/System/Library/CoreServices/SystemVersion.plist"),
        kCFURLPOSIXPathStyle, false);
    if (!url) return;

    CFReadStreamRef readStr = CFReadStreamCreateWithFile(NULL, url);
    CFRelease(url);
    if (!readStr) return;

    if (!CFReadStreamOpen(readStr)) {
        CFRelease(readStr);
        return;
    }

    CFErrorRef outError = NULL;
    CFPropertyListRef propList = CFPropertyListCreateWithStream(
        NULL, readStr, 0, kCFPropertyListImmutable, NULL, &outError);
    CFRelease(readStr);
    if (!propList) {
        CFShow(outError);
        CFRelease(outError);
        return;
    }

    if (CFGetTypeID(propList) != CFDictionaryGetTypeID()) {
        CFRelease(propList);
        return;
    }

    CFDictionaryRef dict = propList;
    CFTypeRef ver = CFDictionaryGetValue(dict, CFSTR("ProductVersion"));
    if (ver) CFRetain(ver);
    CFRelease(dict);
    if (!ver) return;

    if (CFGetTypeID(ver) != CFStringGetTypeID()) {
        CFRelease(ver);
        return;
    }

    CFStringRef verStr = ver;
    // `1 +` for the terminating NUL (\0) character
    CFIndex size = 1 + CFStringGetMaximumSizeForEncoding(
        CFStringGetLength(verStr), kCFStringEncodingASCII);
    // `calloc` initializes the memory with all zero (all \0)
    char * cstr = calloc(1, size);
    if (!cstr) {
        CFRelease(verStr);
        return;
    }

    CFStringGetBytes(ver, CFRangeMake(0, CFStringGetLength(verStr)),
        kCFStringEncodingASCII, '?', false, (UInt8 *)cstr, size, NULL);
    CFRelease(verStr);

    printf("%s\n", cstr);

    int scans = sscanf(cstr, "%u.%u.%u",
        &versions[0], &versions[1], &versions[2]);
    free(cstr);
    // There may only be two values, but only one is definitely wrong.
    // As `version` is `static`, its zero initialized.
    versionOK = (scans >= 2);
}


static
bool macOSVersion (
    unsigned *_Nullable outMajor,
    unsigned *_Nullable outMinor,
    unsigned *_Nullable outBugfix
) {
    dispatch_once_f(&onceToken, NULL, &initMacOSVersion);
    if (versionOK) {
        if (outMajor) *outMajor = versions[0];
        if (outMinor) *outMinor = versions[1];
        if (outBugfix) *outBugfix = versions[2];
    }
    return versionOK;
}

使用dispatch_once_f而不是dispatch_once的原因是块也不是纯C。使用dispatch_once的原因是版本号在系统运行时不能更改,因此在单个应用程序运行期间无需多次读取版本号。另外,读取它不能保证是安全的,并且每次您的应用程序可能需要读取它时重新读取它太昂贵了。
谈到不可靠性,即使这很不可能,读取它可能会失败,在这种情况下,函数返回false并且总是返回false。 我让您在应用程序代码中以有意义的方式处理该情况,因为我不知道知道版本号对您的应用程序有多重要。 如果版本号不关键,则可以绕过它,否则应以用户友好的方式使您的应用程序失败。
注意: 如果您仅需要版本号来根据系统版本执行替代代码,则可能有更好的选择来自行查询版本号。详情请参见此问题

9
或者更简单地说,这就是代码:

NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
NSString *productVersion = [version objectForKey:@"ProductVersion"];
NSLog (@"productVersion =========== %@", productVersion);

我希望这能对某些人有所帮助。

1
谢谢您的评论。有什么解决方法吗,这样我就可以在我的答案中包含它? - neowinston
谢谢,这对一个奇怪的用例很有效。我需要在运行一些iOS测试用例时知道macOS的版本,在模拟器上确定是否支持HEVC/H265编解码器(理论上iOS 11支持它,但仅当模拟器运行在macOS 10.13或更高版本时)。 - Oscar Hierro

8
这实际上是以上答案的总结,为有需要的开发人员提供进一步指导。
OS-X以多种方式在运行时提供其版本。每种方式更适合特定的开发场景。我将尝试总结所有这些方法,并希望如果我忘记了某些内容,其他人可以补充我的答案。
首先,全面列出获得OS版本的方法。
1. uname 命令行工具和函数提供操作系统的Unix(Darwin)版本。虽然这不是操作系统的营销版本,但与之唯一对齐,因此您可以从中推断出OS-X营销版本。
2. sysctl kern.osrelease命令行(或sysctlbyname("kern.osrelease", str, &size, NULL, 0)函数)将提供与uname相同的信息,稍微容易解析。
3. Gestalt(gestaltSystemVersionMajor)(及其"Minor"和BugFix"变体是获取营销OS版本的最古老(预Carbon!)API,仍由长时间弃用支持。可在CoreServices框架中使用C,但不建议使用。
4. NSAppKitVersionNumber是AppKit框架的浮点常量,将提供OS-X Appkit版本(与OS版本对齐),可用于所有链接到AppKit的应用程序。它还提供了所有可能版本的全面枚举(例如NSAppKitVersionNumber10_7_2)。
5. kCFCoreFoundationVersionNumber是CoreFoundation框架的浮点常量,与Appkit对应,可用于所有链接到CoreFoundation的应用程序,包括C、Obj-C和Swift。它也提供了所有OS X发布版本的全面枚举(例如kCFCoreFoundationVersionNumber10_9)。
6. [[NSProcessInfo processInfo] operatingSystemVersionString];是Obj-C中可用于OS-X和iOS应用程序的Cocoa API。
7. 在/System/Library/CoreServices/SystemVersion.plist中有一个资源.plist,其中包含“ProductVersion”键中的OS版本等其他内容。NSProcessInfo从此文件读取其信息,但您可以使用您选择的PList读取API直接执行此操作。
有关每个选项的更多详细信息,请参阅上面的答案。那里有很多信息!

1
数字5不再更新。即使在10.13中,最新的定义也是kCFCoreFoundationVersionNumber10_11_Max,因此在那里找不到10.12和10.13的定义。 - Mecki
现在我最喜欢的第四个已经停止更新了 - 最新值是MacOS SDK 10.13上的NSAppKitVersionNumber10_12_2。真遗憾。 - Motti Shneor

8
如果你有一个应用程序需要在10.10以及之前的版本上运行,这里有一个解决方案:
typedef struct {
        NSInteger majorVersion;
        NSInteger minorVersion;
        NSInteger patchVersion;
} MyOperatingSystemVersion;

if ([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) {
    MyOperatingSystemVersion version = ((MyOperatingSystemVersion(*)(id, SEL))objc_msgSend_stret)([NSProcessInfo processInfo], @selector(operatingSystemVersion));
    // do whatever you want with the version struct here
}
else {
    UInt32 systemVersion = 0;
    OSStatus err = Gestalt(gestaltSystemVersion, (SInt32 *) &systemVersion);
    // do whatever you want with the systemVersion as before
}

请注意,即使是10.9版本似乎也会响应operatingSystemVersion选择器,因此我认为它只是10.9中的私有API(但仍然有效)。
这适用于所有OS X版本,不依赖于字符串解析或文件I/O。

7
不要使用gestaltSystemVersion!如果任何版本组件大于9,这将失败。例如,OS X 10.4.11会返回为10.4.9。相反,使用gestaltSystemVersionMajor、gestaltSystemVersionMinor和gestaltSystemVersionBugFix逐个检索每个版本组件。 - Jake Petroules
2
仅供参考,这仍然依赖于文件I/O内部...两种方法每次调用时都会读取SystemVersion.plist(没有缓存)。 - Jake Petroules
1
由于某些原因,在operatingSystemVersion上执行respondsToSelector检查返回YES,即使我在Mavericks上。 - Z S
1
使用operatingSystemVersion需要什么OS X SDK版本?我正在使用Xcode 4.4.6和SDK是OS X 10.8。我可以在这个SDK上使用它还是需要安装最新的Xcode? - Akhil Shrivastav

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接