Comparar los números de versión en Objective-C

87

Estoy escribiendo una aplicación que recibe datos con elementos y números de versión. Los números tienen el formato "1.0.1" o "1.2.5". ¿Cómo puedo comparar estos números de versión? Creo que primero deben formatearse como una cadena, ¿no? ¿Qué opciones tengo para determinar que "1.2.5" viene después de "1.0.1"?

mlecho
fuente
Escribí esa pequeña biblioteca para comparar fácilmente 2 versiones de Strings en Obj-C. Normalmente en iOS. Tener ejemplos y códigos en la página de GitHub
nembleton
3
Ayuda a aclarar con precisión cuál es el esquema de control de versiones. Algunos pueden tener formatos que requieran lógica adicional.
uchuugaka

Respuestas:

244

Esta es la forma más sencilla de comparar versiones, teniendo en cuenta 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
fuente
6
Estaba usando este método y recientemente descubrí que devuelve (lo que considero) resultados incorrectos al comparar, es decir: 2.4.06 con 2.4.4. Creo que 2.4.06 debería ser menor que 2.4.4, pero tal vez me equivoque ... ¿Alguna idea?
Omer
7
@Omer: ¿Por qué 06 y no 6? Creo que la mayoría de los desarrolladores considerarían que la 2.4.06 es una versión superior a la 2.4.4.
Stephen Melvin
4
Esto es agradable y simple, pero se basa en un esquema de versión muy simple.
uchuugaka
11
@ScottBerrevoets ¡Sin duda espero que no sea así como funciona, ya que eso significaría que "1.2.3" es menor que "1.1.12" (123 <1112)! Como Apple dice cuidadosamente, "Los números dentro de las cadenas se comparan utilizando un valor numérico ". Es decir, se compararán conjuntos de números dentro de cadenas (esencialmente, el componentsSeparatedByStringenfoque). Puede probar esto usted mismo con @"1.8"vs @"1.7.2.3.55"y ver que 1.8 sale adelante.
dooleyo
3
NSNumericSearch cree que "1.0" es menor que "1.0.0". No es lo suficientemente flexible para mis propósitos.
bobics
17

Agregaré mi método, que compara versiones estrictamente numéricas (no a, b, RC, etc.) con cualquier número de componentes.

+ (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
fuente
13

Esta es una expansión de la respuesta de Nathan de Vries para abordar el problema de 1 <1.0 <1.0.0, etc.

En primer lugar, podemos abordar el problema de los ".0" adicionales en nuestra cadena de versión con una NSStringcategoría:

@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 NSStringcategoría anterior , podemos acortar nuestros números de versión para eliminar los .0 innecesarios

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

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

Ahora todavía podemos usar el enfoque maravillosamente simple propuesto por Nathan de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
DonnaLea
fuente
Esta solución, junto con Nathan de Vries, es la mejor y más elegante respuesta.
Dalmazio
¿No diría esto que la 7.4.2 es una versión superior a la 7.5?
Tres
@Tres no. Debido a que NSNumericSearch se pasa como la opción, las cadenas se comparan como números, por lo tanto, 7.4.2 <7.5
DonnaLea
9

Lo hice yo mismo, uso Categoría ...

Fuente..

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

Prueba..

NSString *version1 = @"3.3.1";
NSString *version2 = @"3.12.1";
NSComparisonResult result = [version1 compareVersion:version2];
switch (result) {
    case NSOrderedAscending:
    case NSOrderedDescending:
    case NSOrderedSame:
         break;
    }
Pedro
fuente
¡Increíble! Este es el único ejemplo que usa NSComparisonResult en este hilo que compara 7.28.2 y 7.28 correctamente.
CokePokes
8

Sparkle (el marco de actualización de software más popular para MacOS) tiene una clase SUStandardVersionComparator que hace esto y también tiene en cuenta los números de compilación y los marcadores beta. Es decir, compara correctamente 1.0.5 > 1.0.5b7o 2.0 (2345) > 2.0 (2100). El código solo usa Foundation, por lo que también debería funcionar bien en iOS.

uli testigo
fuente
6

Echa un vistazo a mi categoría NSString que implementa la verificación de versión fácil en github; https://github.com/stijnster/NSString-compareToVersion

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

Esto devolverá un NSComparisonResult que es más preciso que usar;

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

También se agregan ayudantes;

[@"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
fuente
4

Versión 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")
        }

Versión 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
fuente
4

Aquí está el código swift 4.0 + para la comparación de versiones

 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
fuente
3

Pensé en compartir una función que reuní para esto. No es perfecto en absoluto. Eche un vistazo a los ejemplos y resultados. Pero si está verificando sus propios números de versión (lo que tengo que hacer para administrar cosas como las migraciones de bases de datos), esto puede ayudar un poco.

(también, elimine las declaraciones de registro en el método, por supuesto. Están ahí para ayudarlo a ver lo que hace, es todo)

Pruebas:

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

Resultados:

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

note que alpha funciona pero hay que tener mucho cuidado con él. una vez que pasa a alfa en algún momento, no puede extender eso cambiando cualquier otro número menor detrás de él.

Código:

- (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
fuente
3

Mi biblioteca de iOS AppUpdateTracker contiene una categoría NSString para realizar este tipo de comparación. (La implementación se basa en la respuesta de DonnaLea ).

El uso sería el siguiente:

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

Además, puede usarlo para realizar un seguimiento del estado de instalación / actualización de su aplicación:

[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);
}];
Persona maravillosa
fuente
es v4.21 <4.3? if ([thisVersion isGreaterThanOrEqualToVersionString: @ "4.3"])
johndpope
No, 4,21 se considera mayor que 4,3 como 21> 3. Para satisfacer su comparación de igualdad, querría comparar 4,21 con 4,30. Consulte la discusión en los comentarios de la respuesta de Nathan de Vries .
Stunner
0

Glibc tiene una función strverscmpy versionsort... desafortunadamente, no es portátil para el iPhone, pero puedes escribir la tuya con bastante facilidad. Esta reimplementación (no probada) proviene simplemente de leer el comportamiento documentado, y no de leer el código fuente 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;
}
efímero
fuente
2
esto se ve simplemente horrible. Una de las cosas que más me gusta de Objective-C es que en su mayor parte ya no tengo que lidiar con C simple.
Lukas Petr
0

Si sabe que cada número de versión tendrá exactamente 3 enteros separados por puntos, puede analizarlos (por ejemplo, usarlos sscanf(3)) y compararlos:

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
fuente
0

Para verificar la versión en rápido, puede usar lo siguiente

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

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

Espero que te sea de ayuda.

PacienteC
fuente
0

Aquí hay una función recursiva que hace el trabajo con formato de múltiples versiones de cualquier longitud. También funciona para @ "1.0" y @ "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);
    }

}

Muestras de prueba :

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
fuente