Comparer les numéros de version dans Objective-C

87

J'écris une application qui reçoit des données avec des éléments et des numéros de version. Les nombres sont au format "1.0.1" ou "1.2.5". Comment puis-je comparer ces numéros de version? Je pense qu'ils doivent d'abord être formatés sous forme de chaîne, non? Quelles options ai-je pour déterminer que "1.2.5" vient après "1.0.1"?

mlecho
la source
J'ai écrit cette petite bibliothèque pour comparer facilement 2 versions de chaînes en Obj-C. Généralement sous iOS. Avoir des exemples et des codes sur la page GitHub
nembleton
3
Cela aide à clarifier précisément ce qu'est le schéma de gestion des versions. Certains peuvent avoir des formats nécessitant une logique supplémentaire.
uchuugaka

Réponses:

244

C'est le moyen le plus simple de comparer les versions, en gardant à l'esprit que "1" <"1.0" <"1.0.0":

NSString* requiredVersion = @"1.2.0";
NSString* actualVersion = @"1.1.5";

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
Nathan de Vries
la source
6
J'utilisais cette méthode, et j'ai récemment découvert qu'elle renvoie (ce que je considère) de mauvais résultats lors de la comparaison, à savoir: 2.4.06 avec 2.4.4. Je pense que 2.4.06 devrait être inférieur à 2.4.4, mais peut-être que je me trompe ... des pensées?
Omer
7
@Omer: Pourquoi 06 et pas 6? Je pense que la plupart des développeurs considéreraient la 2.4.06 comme une version supérieure à la 2.4.4.
Stephen Melvin
4
C'est sympa et simple mais repose sur un schéma de version très simple.
uchuugaka
11
@ScottBerrevoets J'espère certainement que ce n'est pas ainsi que cela fonctionne, car cela signifierait que "1.2.3" est inférieur à "1.1.12" (123 <1112)! Comme Apple le déclare soigneusement, "les nombres dans les chaînes sont comparés en utilisant une valeur numérique ". Autrement dit, les ensembles de nombres dans les chaînes seront chacun comparés (essentiellement, l' componentsSeparatedByStringapproche). Vous pouvez le tester vous-même avec @"1.8"vs @"1.7.2.3.55"et voir que la 1.8 sort en tête.
dooleyo
3
NSNumericSearch pense que "1.0" est inférieur à "1.0.0". Pas assez flexible pour mes besoins.
bobics
17

J'ajouterai ma méthode, qui compare les versions strictement numériques (pas de a, b, RC etc.) avec n'importe quel nombre de composants.

+ (NSComparisonResult)compareVersion:(NSString*)versionOne toVersion:(NSString*)versionTwo {
    NSArray* versionOneComp = [versionOne componentsSeparatedByString:@"."];
    NSArray* versionTwoComp = [versionTwo componentsSeparatedByString:@"."];

    NSInteger pos = 0;

    while ([versionOneComp count] > pos || [versionTwoComp count] > pos) {
        NSInteger v1 = [versionOneComp count] > pos ? [[versionOneComp objectAtIndex:pos] integerValue] : 0;
        NSInteger v2 = [versionTwoComp count] > pos ? [[versionTwoComp objectAtIndex:pos] integerValue] : 0;
        if (v1 < v2) {
            return NSOrderedAscending;
        }
        else if (v1 > v2) {
            return NSOrderedDescending;
        }
        pos++;
    }

    return NSOrderedSame;
}
nikkiauburger
la source
13

Il s'agit d'une extension de la réponse de Nathan de Vries pour résoudre le problème de 1 <1.0 <1.0.0 etc.

Tout d'abord, nous pouvons résoudre le problème des ".0" supplémentaires sur notre chaîne de version avec une NSStringcatégorie:

@implementation NSString (VersionNumbers)
- (NSString *)shortenedVersionNumberString {
    static NSString *const unnecessaryVersionSuffix = @".0";
    NSString *shortenedVersionNumber = self;

    while ([shortenedVersionNumber hasSuffix:unnecessaryVersionSuffix]) {
        shortenedVersionNumber = [shortenedVersionNumber substringToIndex:shortenedVersionNumber.length - unnecessaryVersionSuffix.length];
    }

    return shortenedVersionNumber;
}
@end

Avec la NSStringcatégorie ci-dessus , nous pouvons raccourcir nos numéros de version pour supprimer les .0 inutiles

NSString* requiredVersion = @"1.2.0";
NSString* actualVersion = @"1.1.5";

requiredVersion = [requiredVersion shortenedVersionNumberString]; // now 1.2
actualVersion = [actualVersion shortenedVersionNumberString]; // still 1.1.5

Maintenant, nous pouvons toujours utiliser l'approche magnifiquement simple proposée par Nathan de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
DonnaLea
la source
Ceci, associé à la solution Nathan de Vries, est la réponse la meilleure et la plus élégante.
Dalmazio
Ne serait-ce pas encore dire que la 7.4.2 est une version supérieure à la 7.5?
Tres
@Tres no. Parce que NSNumericSearch est passé comme option, les chaînes sont comparées sous forme de nombres, donc 7.4.2 <7.5
DonnaLea
9

Je l'ai fait moi-même, utilisez Category.

La source..

@implementation NSString (VersionComparison)
- (NSComparisonResult)compareVersion:(NSString *)version{
    NSArray *version1 = [self componentsSeparatedByString:@"."];
    NSArray *version2 = [version componentsSeparatedByString:@"."];
    for(int i = 0 ; i < version1.count || i < version2.count; i++){
        NSInteger value1 = 0;
        NSInteger value2 = 0;
        if(i < version1.count){
            value1 = [version1[i] integerValue];
        }
        if(i < version2.count){
            value2 = [version2[i] integerValue];
        }
        if(value1  == value2){
            continue;
        }else{
            if(value1 > value2){
                return NSOrderedDescending;
            }else{
                return NSOrderedAscending;
            }
        }
    }
    return NSOrderedSame;
}

Tester..

NSString *version1 = @"3.3.1";
NSString *version2 = @"3.12.1";
NSComparisonResult result = [version1 compareVersion:version2];
switch (result) {
    case NSOrderedAscending:
    case NSOrderedDescending:
    case NSOrderedSame:
         break;
    }
Peter
la source
Impressionnant! C'est le seul exemple utilisant NSComparisonResult sur ce thread qui compare correctement 7.28.2 et 7.28.
CokePokes
8

Sparkle (le framework de mise à jour logicielle le plus populaire pour MacOS) a une classe SUStandardVersionComparator qui fait cela, et prend également en compte les numéros de build et les marqueurs bêta. Ie il compare correctement 1.0.5 > 1.0.5b7ou 2.0 (2345) > 2.0 (2100). Le code utilise uniquement Foundation, il devrait donc fonctionner correctement sur iOS également.

uliwitness
la source
6

Consultez ma catégorie NSString qui implémente une vérification de version facile sur github; https://github.com/stijnster/NSString-compareToVersion

[@"1.2.2.4" compareToVersion:@"1.2.2.5"];

Cela renverra un NSComparisonResult qui est plus précis que l'utilisation;

[@"1.2.2" compare:@"1.2.2.5" options:NSNumericSearch]

Des aides sont également ajoutées;

[@"1.2.2.4" isOlderThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isNewerThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualToVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualOrOlderThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualOrNewerThanVersion:@"1.2.2.5"];
Stijnster
la source
4

Version Swift 2.2:

let currentStoreAppVersion = "1.10.2"
let minimumAppVersionRequired = "1.2.2"
if currentStoreAppVersion.compare(minimumAppVersionRequired, options: NSStringCompareOptions.NumericSearch) ==
            NSComparisonResult.OrderedDescending {
            print("Current Store version is higher")
        } else {
            print("Latest New version is higher")
        }

Version Swift 3:

let currentStoreVersion = "1.1.0.2"
let latestMinimumAppVersionRequired = "1.1.1"
if currentStoreVersion.compare(latestMinimumAppVersionRequired, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
print("Current version is higher")
} else {
print("Latest version is higher")
}
ioopl
la source
4

Voici le code Swift 4.0 + pour la comparaison de versions

 let currentVersion = "1.2.0"

 let oldVersion = "1.1.1"

 if currentVersion.compare(oldVersion, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
        print("Higher")
    } else {
        print("Lower")
    }
Matloob Hasnain
la source
3

Je pensais que je partagerais juste une fonction que j'ai organisée pour cela. Ce n'est pas du tout parfait. Veuillez jeter un œil aux exemples et résultats. Mais si vous vérifiez vos propres numéros de version (ce que je dois faire pour gérer des choses comme les migrations de bases de données), cela peut aider un peu.

(aussi, supprimez les instructions de journal dans la méthode, bien sûr. celles-ci sont là pour vous aider à voir ce qu'elle fait, c'est tout)

Tests:

[self isVersion:@"1.0" higherThan:@"0.1"];
[self isVersion:@"1.0" higherThan:@"0.9.5"];
[self isVersion:@"1.0" higherThan:@"0.9.5.1"];
[self isVersion:@"1.0.1" higherThan:@"1.0"];
[self isVersion:@"1.0.0" higherThan:@"1.0.1"];
[self isVersion:@"1.0.0" higherThan:@"1.0.0"];

// alpha tests
[self isVersion:@"1.0b" higherThan:@"1.0a"];
[self isVersion:@"1.0a" higherThan:@"1.0b"];
[self isVersion:@"1.0a" higherThan:@"1.0a"];
[self isVersion:@"1.0" higherThan:@"1.0RC1"];
[self isVersion:@"1.0.1" higherThan:@"1.0RC1"];

Résultats:

1.0 > 0.1
1.0 > 0.9.5
1.0 > 0.9.5.1
1.0.1 > 1.0
1.0.0 < 1.0.1
1.0.0 == 1.0.0
1.0b > 1.0a
1.0a < 1.0b
1.0a == 1.0a
1.0 < 1.0RC1       <-- FAILURE
1.0.1 < 1.0RC1     <-- FAILURE

notez que alpha fonctionne mais vous devez être très prudent avec cela. une fois que vous passez à l'alpha à un moment donné, vous ne pouvez pas étendre cela en changeant d'autres nombres mineurs derrière.

Code:

- (BOOL) isVersion:(NSString *)thisVersionString higherThan:(NSString *)thatVersionString {

// LOWER
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedAscending) {
    NSLog(@"%@ < %@", thisVersionString, thatVersionString);
    return NO;
}

// EQUAL
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedSame) {
    NSLog(@"%@ == %@", thisVersionString, thatVersionString);
    return NO;
}

NSLog(@"%@ > %@", thisVersionString, thatVersionString);
// HIGHER
return YES;
}
bladnman
la source
3

Ma bibliothèque iOS AppUpdateTracker contient une catégorie NSString pour effectuer ce genre de comparaison. (La mise en œuvre est basée sur la réponse de DonnaLea .)

L'utilisation serait la suivante:

[@"1.4" isGreaterThanVersionString:@"1.3"]; // YES
[@"1.4" isLessThanOrEqualToVersionString:@"1.3"]; // NO

De plus, vous pouvez l'utiliser pour suivre l'état d'installation / de mise à jour de votre application:

[AppUpdateTracker registerForAppUpdatesWithBlock:^(NSString *previousVersion, NSString *currentVersion) {
    NSLog(@"app updated from: %@ to: %@", previousVersion, currentVersion);
}];
[AppUpdateTracker registerForFirstInstallWithBlock:^(NSTimeInterval installTimeSinceEpoch, NSUInteger installCount) {
    NSLog(@"first install detected at: %f amount of times app was (re)installed: %lu", installTimeSinceEpoch, (unsigned long)installCount);
}];
[AppUpdateTracker registerForIncrementedUseCountWithBlock:^(NSUInteger useCount) {
    NSLog(@"incremented use count to: %lu", (unsigned long)useCount);
}];
Stunner
la source
est v4.21 <4.3? if ([thisVersion isGreaterThanOrEqualToVersionString: @ "4.3"])
johndpope
Non, 4,21 est considéré comme supérieur à 4,3 car 21> 3. Afin de satisfaire votre comparaison d'égalité, vous voudrez comparer 4,21 à 4,30. Veuillez voir la discussion dans les commentaires de la réponse de Nathan de Vries .
Stunner
0

Glibc a une fonction strverscmpet versionsort... malheureusement, pas portable sur l'iPhone, mais vous pouvez écrire la vôtre assez facilement. Cette réimplémentation (non testée) vient de la simple lecture du comportement documenté, et non de la lecture du code source de Glibc.

int strverscmp(const char *s1, const char *s2) {
    const char *b1 = s1, *b2 = s2, *e1, *e2;
    long n1, n2;
    size_t z1, z2;
    while (*b1 && *b1 == *b2) b1++, b2++;
    if (!*b1 && !*b2) return 0;
    e1 = b1, e2 = b2;
    while (b1 > s1 && isdigit(b1[-1])) b1--;
    while (b2 > s2 && isdigit(b2[-1])) b2--;
    n1 = strtol(b1, &e1, 10);
    n2 = strtol(b2, &e2, 10);
    if (b1 == e1 || b2 == e2) return strcmp(s1, s2);
    if (n1 < n2) return -1;
    if (n1 > n2) return 1;
    z1 = strspn(b1, "0"), z2 = strspn(b2, "0");
    if (z1 > z2) return -1;
    if (z1 < z2) return 1;
    return 0;
}
éphémère
la source
2
cela a l'air horrible. L'une des choses que j'aime le plus avec Objective-C est que pour la plupart, je n'ai plus à faire face à du C pur.
Lukas Petr le
0

Si vous savez que chaque numéro de version aura exactement 3 entiers séparés par des points, vous pouvez les analyser (par exemple en utilisant sscanf(3)) et les comparer:

const char *version1str = "1.0.1";
const char *version2str = "1.2.5";
int major1, minor1, patch1;
int major2, minor2, patch2;
if(sscanf(version1str, "%d.%d.%d", &major1, &minor1, &patch1) == 3 &&
   sscanf(version2str, "%d.%d.%d", &major2, &minor2, &patch2) == 3)
{
    // Parsing succeeded, now compare the integers
    if(major1 > major2 ||
      (major1 == major2 && (minor1 > minor2 ||
                           (minor1 == minor2 && patch1 > patch2))))
    {
        // version1 > version2
    }
    else if(major1 == major2 && minor1 == minor2 && patch1 == patch2)
    {
        // version1 == version2
    }
    else
    {
        // version1 < version2
    }
}
else
{
    // Handle error, parsing failed
}
Adam Rosenfield
la source
0

Pour vérifier la version dans Swift, vous pouvez utiliser les éléments suivants

switch newVersion.compare(currentversion, options: NSStringCompareOptions.NumericSearch) {
    case .OrderedDescending:
        println("NewVersion available  ")
        // Show Alert Here

    case .OrderedAscending:
        println("NewVersion Not available  ")
    default:
        println("default")
    }

J'espère que cela pourrait être utile.

PatientC
la source
0

Voici une fonction récursive qui fonctionne avec le formatage de plusieurs versions de n'importe quelle longueur. Cela fonctionne également pour @ "1.0" et @ "1.0.0"

static inline NSComparisonResult versioncmp(const NSString * a, const NSString * b)
{
    if ([a isEqualToString:@""] && [b isEqualToString:@""]) {
        return NSOrderedSame;
    }

    if ([a isEqualToString:@""]) {
        a = @"0";
    }

    if ([b isEqualToString:@""]) {
        b = @"0";
    }

    NSArray<NSString*> * aComponents = [a componentsSeparatedByString:@"."];
    NSArray<NSString*> * bComponents = [b componentsSeparatedByString:@"."];
    NSComparisonResult r = [aComponents[0] compare:bComponents[0] options:NSNumericSearch];

    if(r != NSOrderedSame) {
        return r;
    } else {
        NSString* newA = (a.length == aComponents[0].length) ? @"" : [a substringFromIndex:aComponents[0].length+1];
        NSString* newB = (b.length == bComponents[0].length) ? @"" : [b substringFromIndex:bComponents[0].length+1];
        return versioncmp(newA, newB);
    }

}

Échantillons de test:

versioncmp(@"11.5", @"8.2.3");
versioncmp(@"1.5", @"8.2.3");
versioncmp(@"1.0", @"1.0.0");
versioncmp(@"11.5.3.4.1.2", @"11.5.3.4.1.2");
Neimsz
la source