Compare os números da versão em Objective-C

87

Estou escrevendo um aplicativo que recebe dados com itens e números de versão. Os números são formatados como "1.0.1" ou "1.2.5". Como posso comparar esses números de versão? Eu acho que eles têm que ser formatados como string primeiro, não? Que opções tenho para determinar que "1.2.5" vem depois de "1.0.1"?

mlecho
fonte
Eu escrevi aquela pequena biblioteca para comparar facilmente 2 versões Strings in Obj-C. Normalmente no iOS. Tenha exemplos e códigos na página
nembleton
3
Isso ajuda a esclarecer precisamente qual é o esquema de controle de versão. Alguns podem ter formatos que requerem lógica adicional.
uchuugaka de

Respostas:

242

Esta é a maneira mais simples de comparar versões, tendo em mente 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
fonte
6
Eu estava usando esse método e recentemente descobri que ele retorna (o que considero) resultados errados ao comparar, por exemplo: 2.4.06 com 2.4.4. Eu acredito que 2.4.06 deveria ser menor que 2.4.4, mas talvez eu esteja errado ... alguma ideia?
Omer
7
@Omer: Por que 06 e não 6? Acho que a maioria dos desenvolvedores consideraria 2.4.06 uma versão superior a 2.4.4.
Stephen Melvin
4
Isso é bom e simples, mas depende de um esquema de versão muito simples.
uchuugaka
11
@ScottBerrevoets Eu certamente espero que não seja assim que funciona, pois isso significaria que "1.2.3" é menor que "1.1.12" (123 <1112)! Como a Apple afirma cuidadosamente, "os números dentro das strings são comparados usando valores numéricos ". Ou seja, cada um dos conjuntos de números dentro das strings será comparado (essencialmente, a componentsSeparatedByStringabordagem). Você mesmo pode testar isso com @"1.8"vs @"1.7.2.3.55"e ver que 1.8 sai na frente.
dooleyo
3
NSNumericSearch pensa que "1.0" é menor que "1.0.0". Não é bastante flexível para meus propósitos.
bobics
17

Adicionarei meu método, que compara versões estritamente numéricas (não a, b, RC etc.) com qualquer 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
fonte
13

Esta é uma expansão da resposta de Nathan de Vries para resolver o problema de 1 <1,0 <1,0,0 etc.

Em primeiro lugar, podemos resolver o problema de ".0" extras em nossa string de versão com uma 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

Com a NSStringcategoria acima , podemos encurtar nossos números de versão para eliminar os .0s desnecessários

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

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

Agora ainda podemos usar a abordagem lindamente simples proposta por Nathan de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
DonnaLea
fonte
Isso, junto com a solução Nathan de Vries, é a melhor e mais elegante resposta.
Dalmazio
Isso ainda não diria que 7.4.2 é uma versão superior a 7.5?
Três de
@Tres no. Como NSNumericSearch é passado como opção, as strings são comparadas como números, portanto, 7.4.2 <7.5
DonnaLea
9

Eu mesmo fiz, uso 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;
}

Teste..

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
fonte
Impressionante! Este é o único exemplo usando NSComparisonResult neste thread que compara 7.28.2 e 7.28 corretamente.
CokePokes
8

Sparkle (a estrutura de atualização de software mais popular para MacOS) tem uma classe SUStandardVersionComparator que faz isso e também leva em consideração números de compilação e marcadores beta. 1.0.5 > 1.0.5b7Ou seja, compara corretamente ou 2.0 (2345) > 2.0 (2100). O código usa apenas Foundation, então deve funcionar bem no iOS também.

uliwitness
fonte
6

Confira minha categoria NSString que implementa verificação de versão fácil no github; https://github.com/stijnster/NSString-compareToVersion

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

Isso retornará um NSComparisonResult que é mais preciso do que usar;

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

Ajudantes também são adicionados;

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

Versão do 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")
        }

Versão 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
fonte
4

Aqui está o código do swift 4.0 + para comparação de versões

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

Pensei em compartilhar uma função que reuni para isso. Não é nada perfeito. Veja os exemplos e resultados. Mas se você estiver verificando seus próprios números de versão (o que tenho que fazer para gerenciar coisas como migrações de banco de dados), isso pode ajudar um pouco.

(também, remova as declarações de log do método, é claro. Elas estão lá para ajudá-lo a ver o que faz, é tudo)

Testes:

[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

observe que o alfa funciona, mas você deve ter muito cuidado com ele. uma vez que você vá para alfa em algum ponto, você não pode estender isso alterando quaisquer outros números menores por trás dele.

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

Minha biblioteca iOS AppUpdateTracker contém uma categoria NSString para realizar esse tipo de comparação. (A implementação é baseada na resposta de DonnaLea .)

O uso seria o seguinte:

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

Além disso, você pode usá-lo para controlar o status de instalação / atualização do seu aplicativo:

[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
fonte
é v4.21 <4.3? if ([thisVersion isGreaterThanOrEqualToVersionString: @ "4.3"])
johndpope
Não, 4,21 é considerado maior que 4,3 como 21> 3. Para satisfazer sua comparação de igualdade, você gostaria de comparar 4,21 com 4,30. Por favor, veja a discussão nos comentários da resposta de Nathan de Vries .
Stunner de
0

Glibc tem uma função strverscmpe versionsort... infelizmente, não é portátil para o iPhone, mas você pode escrever a sua com bastante facilidade. Esta reimplementação (não testada) vem apenas da leitura do comportamento documentado, e não da leitura do código-fonte do 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
fonte
2
isso parece simplesmente horrível. Uma das coisas que mais gosto em Objective-C é que, na maior parte do tempo, não preciso mais lidar com C puro.
Lukas Petr
0

Se você souber que cada número de versão terá exatamente 3 inteiros separados por pontos, você pode analisá-los (por exemplo, usando sscanf(3)) e compará-los:

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

Para verificar a versão rapidamente, você pode usar o seguinte

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 possa ser útil.

Paciente C
fonte
0

Aqui está uma função recursiva que faz o trabalho com a formatação de várias versões de qualquer comprimento. Também funciona para @ "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);
    }

}

Amostras de teste:

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
fonte