Confronta i numeri di versione in Objective-C


87

Sto scrivendo un'applicazione che riceve dati con elementi e numeri di versione. I numeri sono formattati come "1.0.1" o "1.2.5". Come posso confrontare questi numeri di versione? Penso che debbano essere formattati prima come una stringa, no? Quali opzioni ho per stabilire che "1.2.5" viene dopo "1.0.1"?


Ho scritto quella piccola libreria per confrontare facilmente 2 versioni di stringhe in Obj-C. Tipicamente in iOS. Avere esempi e codici sulla pagina GitHub
nembleton

3
Aiuta a chiarire esattamente qual è lo schema di controllo delle versioni. Alcuni potrebbero avere formati che richiedono logica aggiuntiva.
uchuugaka

Risposte:


244

Questo è il modo più semplice per confrontare le versioni, tenendo presente che "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
}

6
Stavo usando questo metodo e di recente ho scoperto che restituisce (ciò che considero) risultati errati durante il confronto, ad esempio: 2.4.06 con 2.4.4. Credo che 2.4.06 dovrebbe essere inferiore a 2.4.4, ma forse mi sbaglio ... qualche idea?
Omer

7
@Omer: Perché 06 e non 6? Penso che la maggior parte degli sviluppatori considererebbe la 2.4.06 una versione successiva alla 2.4.4.
Stephen Melvin

4
Questo è carino e semplice ma si basa su uno schema di versioni molto semplice.
uchuugaka

11
@ScottBerrevoets Spero certamente che non funzioni così, poiché ciò significherebbe che "1.2.3" è inferiore a "1.1.12" (123 <1112)! Come Apple afferma attentamente, "I numeri all'interno di stringhe vengono confrontati utilizzando un valore numerico ". Cioè, verranno confrontati gruppi di numeri all'interno di stringhe (essenzialmente, l' componentsSeparatedByStringapproccio). Puoi testarlo tu stesso con @"1.8"vs @"1.7.2.3.55"e vedere che 1.8 esce avanti.
dooleyo

3
NSNumericSearch ritiene che "1.0" sia minore di "1.0.0". Non abbastanza flessibile per i miei scopi.
bobics

17

Aggiungerò il mio metodo, che confronta le versioni strettamente numeriche (no a, b, RC ecc.) Con un numero qualsiasi di componenti.

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

13

Questa è un'espansione della risposta di Nathan de Vries per affrontare il problema di 1 <1.0 <1.0.0 ecc.

Prima di tutto possiamo affrontare il problema degli ".0" extra sulla nostra stringa di versione con una NSStringcategoria:

@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

Con la NSStringcategoria precedente possiamo abbreviare i nostri numeri di versione per eliminare gli .0 non necessari

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

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

Ora possiamo ancora usare l'approccio meravigliosamente semplice proposto da Nathan de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}

Questa soluzione abbinata a Nathan de Vries è la risposta migliore e più elegante.
Dalmazio

Questo non direbbe ancora che la 7.4.2 è una versione superiore alla 7.5?
Tres

@Tres no. Poiché NSNumericSearch viene passato come opzione, le stringhe vengono confrontate come numeri, quindi 7.4.2 <7.5
DonnaLea

9

L'ho fatto da solo, usa Categoria ..

Fonte..

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

Test..

NSString *version1 = @"3.3.1";
NSString *version2 = @"3.12.1";
NSComparisonResult result = [version1 compareVersion:version2];
switch (result) {
    case NSOrderedAscending:
    case NSOrderedDescending:
    case NSOrderedSame:
         break;
    }

Eccezionale! Questo è l'unico esempio che utilizza NSComparisonResult su questo thread che confronta correttamente 7.28.2 e 7.28.
CokePokes

8

Sparkle (il framework di aggiornamento software più popolare per MacOS) ha una classe SUStandardVersionComparator che esegue questa operazione e tiene conto anche dei numeri di build e dei marcatori beta. Cioè confronta correttamente 1.0.5 > 1.0.5b7o 2.0 (2345) > 2.0 (2100). Il codice utilizza solo Foundation, quindi dovrebbe funzionare bene anche su iOS.


6

Controlla la mia categoria NSString che implementa un facile controllo della versione su GitHub; https://github.com/stijnster/NSString-compareToVersion

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

Ciò restituirà un NSComparisonResult che è più accurato rispetto all'utilizzo;

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

Vengono aggiunti anche aiutanti;

[@"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"];

4

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

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

4

Ecco il codice swift 4.0 + per il confronto delle versioni

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

3

Ho pensato di condividere solo una funzione che ho messo insieme per questo. Non è affatto perfetto. Si prega di dare un'occhiata agli esempi e ai risultati. Ma se stai controllando i tuoi numeri di versione (cosa che devo fare per gestire cose come le migrazioni del database), questo potrebbe aiutare un po '.

(inoltre, rimuovi le istruzioni di registro nel metodo, ovviamente. quelle sono lì per aiutarti a vedere cosa fa è tutto)

Test:

[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"];

Risultati:

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

nota che alpha funziona ma devi stare molto attento con esso. una volta che si diventa alfa ad un certo punto non è possibile estenderlo modificando qualsiasi altro numero minore dietro di esso.

Codice:

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

3

La mia libreria iOS AppUpdateTracker contiene una categoria NSString per eseguire questo tipo di confronto. (L'implementazione si basa sulla risposta di DonnaLea .)

L'utilizzo sarebbe il seguente:

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

Inoltre, puoi utilizzarlo per tenere traccia dello stato di installazione / aggiornamento della tua app:

[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);
}];

è v4.21 <4.3? if ([thisVersion isGorgeousThanOrEqualToVersionString: @ "4.3"])
johndpope

No, 4.21 è considerato maggiore di 4.3 come 21> 3. Per soddisfare il tuo confronto di uguaglianza, dovresti confrontare 4.21 con 4.30. Si prega di vedere la discussione nei commenti della risposta di Nathan de Vries .
Storditore

0

Glibc ha una funzione strverscmpe versionsort... sfortunatamente non è portabile su iPhone, ma puoi scriverne una tua abbastanza facilmente. Questa reimplementazione (non testata) deriva dalla semplice lettura del comportamento documentato e non dalla lettura del codice sorgente di 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;
}

2
questo sembra semplicemente orribile. Una delle cose che mi piace di più di Objective-C è che per la maggior parte non ho più a che fare con il C normale.
Lukas Petr,

0

Se sai che ogni numero di versione avrà esattamente 3 numeri interi separati da punti, puoi analizzarli (ad esempio usando sscanf(3)) e confrontarli:

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
}

0

Per controllare la versione in modo rapido è possibile utilizzare quanto segue

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

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

Spero possa essere utile.


0

Ecco una funzione ricorsiva che funziona con la formattazione di più versioni di qualsiasi lunghezza. Funziona anche per @ "1.0" e @ "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);
    }

}

Campioni di prova:

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");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.