Как определить версию ОС во время выполнения в OS X или iOS (без использования Gestalt)?

Функция Gestalt(), расположенная в CarbonCore/OSUtils.h было объявлено устаревшим с OS X 10.8 Mountain Lion.

Я часто использую эту функцию для тестирования версии операционной системы OS X во время выполнения (см. Игрушечный пример ниже).

Какой еще 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);
}

17 ответов

Решение

На 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 и позже, оба operatingSystemVersion а также isOperatingSystemAtLeastVersion: существуют на OS X 10.9 ( вероятно, 10.9.2) и работают как положено. Это означает, что вы не должны проверять, если NSProcessInfo отвечает на эти селекторы, чтобы проверить, работаете ли вы на OS X 10.9 или 10.10.

На iOS эти методы эффективно доступны только с iOS 8.0.

В командной строке:

$ 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);

Дарвин версия до выпуска 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

Существует значение NSAppKitVersionNumber, которое можно использовать для проверки различных версий AppKit, хотя они не точно соответствуют версиям ОС.

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

Есть какао API. Вы можете получить строку версии OS X из класса NSProcessInfo.

Код для получения строки версии операционной системы приведен ниже.

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

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

// === >> Версия 10.8.2 (сборка 12C2034) результирующее значение

Это не считается устаревшим.

Вы можете легко получить мажорную, минорную и исправленную версии операционной системы, используя 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];

Существует также 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.")
}

Или, проще говоря, вот код:

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

Я надеюсь, что это помогает кому-то.

Gestalt() это чистый C API. Принятый ответ предполагает использование NSProcessInfo но для этого требуется код Objective-C, и его нельзя использовать, если по какой-то причине требуется чистый C. То же самое относится и к NSAppKitVersionNumber, который также больше не обновляется Apple с 10.13.4. Используя константу kCFCoreFoundationVersionNumber было возможно в коде C, но эта константа больше не обновлялась с 10.11.4. чтение kern.osrelease с помощью sysctl возможно в C, но требует таблицы сопоставления или полагается на текущую схему управления версиями ядра и, таким образом, не является надежной для будущей версии, поскольку схема версии ядра может измениться, и таблица сопоставления не может знать будущие версии.

Официальный и рекомендуемый способ от Apple, чтобы прочитать /System/Library/CoreServices/SystemVersion.plist поскольку это также где Gestalt() получает номер версии, это также где NSProcessInfo() получает номер версии, это также где инструмент командной строки sw_vers получает номер версии, и это даже там, где новый @available(macOS 10.x, ...) Функция компилятора получает номер версии (я углубился в систему, чтобы узнать об этом). Я не удивлюсь, если даже Свифт #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 в том, что блоки тоже не чистые. Причина, по которой диспетчер используется один раз, заключается в том, что номер версии не может измениться во время работы системы, поэтому нет необходимости читать номер версии более одного раза за один запуск приложения. Кроме того, его чтение не гарантирует отказоустойчивость и слишком дорого перечитывает его каждый раз, когда ваше приложение может потребовать этого.

Говоря о том, что он не является отказоустойчивым, даже если это маловероятно, его чтение может, конечно, потерпеть неудачу, и в этом случае функция возвращает false и всегда вернусь false, Я оставляю на ваше усмотрение осмысленное рассмотрение этого случая в коде вашего приложения, потому что я не могу понять, насколько важно знать номер версии для вашего приложения. Если номер версии не является критичным, возможно, вы можете обойти его, в противном случае вы должны заставить свое приложение отказывать в удобной для пользователя форме.

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

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

Во-первых, исчерпывающий список способов получения версии ОС.

  1. uname инструмент командной строки и функция обеспечивает Unix (Дарвин) версию ОС. Хотя это не маркетинговая версия ОС, она однозначно соответствует ей, поэтому из нее можно вывести маркетинговую версию OS-X.
  2. sysctl kern.osrelease командная строка (или sysctlbyname("kern.osrelease", str, &size, NULL, 0) function) будет предоставлять ту же информацию, что и uname, немного легче разобрать.
  3. Gestalt(gestaltSystemVersionMajor) (с этими "Minor" а также BugFix"Варианты - это самый старый (до-Carbon!) API для получения маркетинговой версии ОС, все еще поддерживаемый давно устаревшим. Доступен в C из инфраструктуры CoreServices, но не рекомендуется.
  4. NSAppKitVersionNumber является константой с плавающей запятой платформы AppKit, которая будет предоставлять версию OS-X Appkit (в соответствии с версией ОС), доступную для всех приложений, которые ссылаются на AppKit. Он также предоставляет полный перечень всех возможных версий (например, NSAppKitVersionNumber10_7_2)
  5. kCFCoreFoundationVersionNumber является плавающей константой платформы CoreFoundation, идентичной аналогу Appkit, доступной для всех приложений, связанных с CoreFoundation, как в C, Obj-C, так и в Swift. Он также предоставляет исчерпывающее перечисление для всех выпущенных версий OS X (например, kCFCoreFoundationVersionNumber10_9)
  6. [[NSProcessInfo processInfo] operatingSystemVersionString]; API-интерфейс Cocoa, доступный в Obj-C для приложений OS-X и iOS.
  7. Существует ресурс.plist в /System/Library/CoreServices/SystemVersion.plist который, помимо прочего, содержит версию ОС в ключе "ProductVersion". NSProcessInfo считывает информацию из этого файла, но вы можете сделать это напрямую, используя выбранный вами API чтения PList.

Для более подробной информации о каждом варианте - пожалуйста, обратитесь к ответам выше. Там много информации!

Если у вас есть приложение, которое должно работать на 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, поэтому я думаю, что это был только частный API в 10.9 (но все еще работает).

Это работает во всех версиях OS X и не зависит от анализа строк или файлового ввода-вывода.

Это то, что я использую:

NSInteger osxVersion;
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_6) {
    //10.6.x or earlier systems
    osxVersion = 106;
    NSLog(@"Mac OSX Snow Leopard");
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_7) {
    /* On a 10.7 - 10.7.x system */
    osxVersion = 107;
    NSLog(@"Mac OSX Lion");
} else if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_8) {
    /* On a 10.8 - 10.8.x system */
    osxVersion = 108;
    NSLog(@"Mac OSX Moutain Lion");
} else {
    /* 10.9 or later system */
    osxVersion = 109;
    NSLog(@"Mac OSX: Mavericks or Later");
}

Рекомендуется в примечаниях к выпуску AppKit

Чтение /System/Library/CoreServices/SystemVersion.plist невозможно, если приложение находится в "песочнице"

Играя с sysctl на macOS сегодня наткнулся kern.osproductversion которая действительно содержит текущую версию ОС. На моем Mac под управлением Mojave я получаю:

$ sysctl kern.osproductversion
kern.osproductversion: 10.14.3

Конечно, это значение также можно получить программно:

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

Эта фиксация базы кода ядра xnu указывает на то, что osproductversion это недавнее дополнение, появившееся в середине 2018 года. Я успешно протестировал его на 10.14.3 и 10.13.6.

В общем, лучший программный подход, не относящийся к какао, по моему мнению, это проверить kern.osproductversion дает действительный результат и возвращается к kern.osrelease и ручное отображение, как описано в этом ответе в противном случае.

Там в uname(3):

uname() функция хранит нуль-терминированные строки информации, идентифицирующие текущую систему, в структуре, на которую ссылается name,

utsname структура определяется в <sys/utsname.h> заголовочный файл, и содержит следующие члены:

  • sysname - Имя операционной системы реализации.
  • nodename - Сетевое имя этой машины.
  • release - Уровень выпуска операционной системы.
  • version - Уровень версии операционной системы.
  • machine - аппаратная платформа оборудования.

Поздно к игре, но я в конечном итоге здесь в поисках ответа. Для чего это стоит, может быть, это полезно для кого-то другого;

В прошлом я использовал подход командной строки:

sw_vers

Что приводит к:

ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G65

Каждая строка может быть запрошена индивидуально (обратите внимание на обозначение верблюда):

sw_vers -productVersion
10.13.6

sw_vers -productName
Mac OS X

sw_vers -buildVersion
17G65

Сказав это, спасибо за все другие решения, перечисленные здесь...

// мои два цента за Swift (мультиплатформенный)

#if os(iOS)
import UIKit
#elseif os(OSX)
import Cocoa
#endif



func platform() -> String {
    var systemInfo = utsname()
    uname(&systemInfo)
    let size = Int(_SYS_NAMELEN) // is 32, but posix AND its init is 256....
    
    let s = withUnsafeMutablePointer(to: &systemInfo.machine) {p in
        //    let s = withUnsafeMutablePointer(to: &systemInfo.nodename) {p in
        
        p.withMemoryRebound(to: CChar.self, capacity: size, {p2 in
            return String(cString: p2)
        })
        
    }
    return s
}



func AppName()->String{
    let bund = Bundle.main
    if let displayName = bund.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
        if displayName.count>0{
            return displayName
        }
    }
    
    if let name = bund.object(forInfoDictionaryKey: "CFBundleName") as? String {
        return name
    }
    return "no AppName"
}


func buildVers()->String{
    
    let bund = Bundle.main
    let vers = bund.object(forInfoDictionaryKey: "CFBundleVersion") as! String
    return  vers
}


func AppleStoreVers()->String{
    
    if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String {
        return version
    }
    return "no.vers."
}



#if os(iOS)
func systemVersion()->String{
    let  v = UIDevice.current.systemVersion
    return v
}
#elseif os(OSX)
#endif

Обновление для macOS Catalina и выше

      20.x.x  macOS 11.X.X  BigSur
19.x.x  macOS 10.15.x Catalina
18.x.x  macOS 10.14.x Mojave
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

2022, МакОС

      let a = ProcessInfo.processInfo.operatingSystemVersion

let b = ProcessInfo.processInfo.operatingSystemVersionString

iOS:

      var a = UIDevice.current.systemVersion

let b = ProcessInfo.processInfo.operatingSystemVersion

let c = ProcessInfo.processInfo.operatingSystemVersionString
Другие вопросы по тегам