web-dev-qa-db-fra.com

Comment déterminer la version du système d'exploitation au moment de l'exécution sous OS X ou iOS (sans utiliser Gestalt)?

La fonction Gestalt () située dans CarbonCore/OSUtils.h est obsolète depuis OS X 10.8 Mountain Lion.

J'utilise souvent cette fonction pour tester la version du système d'exploitation OS X au moment de l'exécution (voir l'exemple ci-dessous).

Quelle autre API pourrait être utilisée pour vérifier la version du système d'exploitation OS X au moment de l'exécution dans une application Cocoa?

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);
}
62
Todd Ditchendorf

Sous OS X 10.10 (et iOS 8.0), vous pouvez utiliser [[NSProcessInfo processInfo] operatingSystemVersion] qui renvoie une structure NSOperatingSystemVersion, définie comme suit:

typedef struct {
    NSInteger majorVersion;
    NSInteger minorVersion;
    NSInteger patchVersion;
} NSOperatingSystemVersion;

Il existe également une méthode dans NSProcessInfo qui effectuera la comparaison pour vous:

- (BOOL)isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion)version

Méfiez-vous, bien que documenté pour être disponible dans OS X 10.10 et ultérieur, operatingSystemVersion et isOperatingSystemAtLeastVersion: existent sur OS X 10.9 ( probablement 10.9.2 ) et fonctionnent comme prévu. Cela signifie que vous ne devez pas vérifier si NSProcessInfo répond à ces sélecteurs pour vérifier si vous utilisez OS X 10.9 ou 10.10.

Sur iOS, ces méthodes ne sont effectivement disponibles que depuis iOS 8.0.

61
0xced

Sur la ligne de commande:

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

Par programme:

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

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

Version Darwin vers 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

Un exemple pour obtenir et tester des versions:

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


}

Résultat sur ma machine macOs High Sierra 10.13.2 

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

Il existe la valeur NSAppKitVersionNumber que vous pouvez utiliser pour vérifier les différentes versions d'AppKit, bien qu'elles ne correspondent pas exactement aux versions du système d'exploitation.

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

Il y a une API de cacao. Vous pouvez obtenir une chaîne de version os X de la classe NSProcessInfo. 

Le code pour obtenir la chaîne de version du système d'exploitation est ci-dessous.

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

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

// === >> Valeur de résultat de la version 10.8.2 (Build 12C2034)

Ce n'est pas obsolète. 

16
DHL

Il existe également kCFCoreFoundationVersionNumber qui peut être utilisé si vous devez uniquement vérifier la version minimale à prendre en charge. Cela a l'avantage de fonctionner depuis la version 10.1 et peut être fait en C, C++ et Objective-C.

Par exemple, pour vérifier 10.10 ou plus:

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

Vous devrez vous connecter au framework CoreFoundation (ou Foundation).

Cela fonctionne aussi exactement de la même manière à Swift. Voici un autre exemple:

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.")
}
11
kainjow

Ou, pour le dire plus simplement, voici le code:

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

J'espère que ça aidera quelqu'un.

9
Winston

Vous pouvez facilement obtenir la version majeure, mineure, du correctif du système d'exploitation à l'aide de 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];
9
Vikas Bansal

Si vous avez une application devant fonctionner sur la version 10.10 ainsi que sur les versions précédentes, voici une solution:

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
}

Notez que même 10.9 semble répondre au sélecteur operatingSystemVersion, je pense donc qu'il s'agissait simplement d'une API privée dans 10.9 (mais qu'elle fonctionne toujours).

Cela fonctionne sur toutes les versions de OS X et ne repose pas sur l'analyse de chaînes ni sur les entrées/sorties de fichiers.

7
SentientAI

C'est ce que j'utilise: 

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");
}

Il est recommandé dans Notes de publication d'AppKit

La lecture de /System/Library/CoreServices/SystemVersion.plist n'est pas possible si l'application est en mode bac à sable

6
Tibidabo

Ceci est en fait une compilation des réponses ci-dessus, avec quelques indications supplémentaires pour le développeur dans le besoin.

OS-X fournit sa version au moment de l'exécution de plusieurs manières. Chaque solution correspond mieux à un scénario de développement spécifique. Je vais essayer de les résumer en espérant que d’autres compléteront ma réponse si j’oublie quelque chose.

Tout d'abord, la liste complète des moyens d'obtenir la version du système d'exploitation.

  1. L'outil et la fonction de ligne de commande uname fournissent la version unix (darwin) du système d'exploitation. Bien qu'il ne s'agisse pas de la version marketing du système d'exploitation, celle-ci est alignée de manière unique. Vous pouvez donc en déduire la version marketing d'OS-X.
  2. La ligne de commande sysctl kern.osrelease (ou la fonction sysctlbyname("kern.osrelease", str, &size, NULL, 0)) fournira les mêmes informations que uname, plus facile à analyser.
  3. Gestalt(gestaltSystemVersionMajor) (avec ses variantes "Minor" et BugFix "est la plus ancienne API (antérieure à Carbon!) pour obtenir la version de système d’exploitation marketing, toujours prise en charge par une version obsolète. Disponible en C du framework CoreServices, mais non recommandé.
  4. NSAppKitVersionNumber est une constante flottante du framework AppKit, qui fournira la version OS-X Appkit (alignée sur la version du système d'exploitation), disponible pour toutes les applications reliées à AppKit. Il fournit également une énumération complète de toutes les versions possibles (par exemple NSAppKitVersionNumber10_7_2).
  5. kCFCoreFoundationVersionNumber est une constante float de la structure CoreFoundation, identique à son homologue Appkit, disponible pour toutes les applications liées à CoreFoundation, en C, Obj-C et Swift. Il fournit également une énumération complète de toutes les versions publiées par OS X (par exemple, kCFCoreFoundationVersionNumber10_9).
  6. [[NSProcessInfo processInfo] operatingSystemVersionString]; est une API Cocoa disponible dans Obj-C pour les applications OS-X et iOS.
  7. Il existe une ressource .plist dans /System/Library/CoreServices/SystemVersion.plist qui, entre autres choses, contient la version du système d'exploitation dans la clé "ProductVersion". NSProcessInfo lit ses informations à partir de ce fichier, mais vous pouvez le faire directement en utilisant votre API de choix de lecture de liste.

Pour plus de détails sur chaque option, veuillez consulter les réponses ci-dessus. Il y a beaucoup d'informations là-bas!

5
Motti Shneor

Il y a uname(3) :

La fonction uname() enregistre dans la structure référencée par name des chaînes d'informations à terminaison nulle identifiant le système actuel.

La structure utsname est définie dans le fichier d'en-tête <sys/utsname.h>, et contient les membres suivants:

  • sysname - Nom de l'implémentation du système d'exploitation.
  • nodename - Nom du réseau de cette machine.
  • release - Niveau de publication du système d'exploitation.
  • version - Niveau de version du système d'exploitation.
  • machine - Plate-forme matérielle de la machine.
5
Carl Norum

En jouant avec sysctl sur macOS aujourd'hui, je suis tombé sur kern.osproductversion qui contient en effet la version actuelle du système d'exploitation. Sur mon Mac sous Mojave, je reçois:

$ sysctl kern.osproductversion
kern.osproductversion: 10.14.3

La valeur peut également être récupérée par programme, bien sûr:

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

Ce commit de la base de code du noyau xnu indique que osproductversion est un ajout récent intervenu au milieu de 2018. Je l’ai testé avec succès les versions 10.14.3 et 10.13.6.

Globalement, la meilleure approche programmatique non-cacao serait, à mon avis, de vérifier si kern.osproductversion produit un résultat valide et de revenir à kern.osrelease et à un mappage manuel comme décrit dans cette réponse sinon.

1
bfx

Tard au début du match, mais je me suis retrouvé ici à chercher une réponse également… __.

Dans le passé, j'ai utilisé l'approche en ligne de commande: 

sw_vers

Ce qui résulte en:

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

Chaque ligne peut être demandée individuellement (attention à la notation camelback):

sw_vers -productVersion
10.13.6

sw_vers -productName
Mac OS X

sw_vers -buildVersion
17G65

Cela dit, merci pour toutes les autres solutions énumérées ici ...

0
Hanzaplastique

Gestalt() est une API C pure. La réponse acceptée suggère d'utiliser NSProcessInfo mais cela nécessite du code Objective-C et ne peut pas être utilisé si du C pur est requis pour une raison quelconque. Il en va de même pour NSAppKitVersionNumber, qui n'est plus mis à jour par Apple depuis 10.13.4. L'utilisation de la constante kCFCoreFoundationVersionNumber était possible dans le code C, mais cette constante n'a plus été mise à jour depuis 10.11.4. La lecture de kern.osrelease à l'aide de sysctl est possible en C, mais requiert une table de mappage ou repose sur le schéma de gestion de version du noyau actuel. Elle n'est donc pas fiable pour les versions futures car le schéma de version du noyau peut changer et une table de mappage ne peut pas être connue. futures versions.

La manière officielle et recommandée de Apple est de lire /System/Library/CoreServices/SystemVersion.plist car c'est aussi où Gestalt() obtient le numéro de version, c'est aussi où NSProcessInfo() obtient le numéro de version. C’est également à partir de là que l’outil en ligne de commande sw_vers obtient le numéro de version et c’est même le cas où la nouvelle fonction de compilateur @available(macOS 10.x, ...) obtient le numéro de version (je me suis plongé dans le système pour ce). Je ne serais pas surpris si même #available(macOS 10.x, *) de Swift obtiendrait le numéro de version à partir de là.

Il n’existe pas d’appel simple en C pour lire le numéro de version à partir de là. J’ai donc écrit le code C pur suivant, qui ne nécessite que CoreFoundation framework, qui est lui-même un framework C pur:

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

La raison d'utiliser un dispatch_once_f au lieu de dispatch_once est que les blocs ne sont pas non plus du pur C. La raison d'utiliser la répartition une seule fois est que le numéro de version ne peut pas changer lorsque le système est en cours d'exécution. Il n'y a donc aucune raison de lire le numéro de version plusieurs fois au cours d'une seule exécution d'application. En outre, la lecture de cette dernière n'est pas garantie et trop onéreuse à relire à chaque fois que votre application peut en avoir besoin.

Parlant de ne pas être à l'abri des pannes, même si c'est peu probable, sa lecture peut bien sûr échouer, auquel cas la fonction retourne false et retournera toujours false. Je vous laisse le soin de gérer ce cas de manière significative dans le code de votre application, car je ne sais pas à quel point il est essentiel de connaître le numéro de version de votre application. Si le numéro de version n'est pas critique, vous pouvez peut-être le contourner, sinon vous devriez faire échouer votre application de manière conviviale.

0
Mecki