Porównaj numery wersji w Objective-C

87

Piszę aplikację, która otrzymuje dane z pozycjami i numerami wersji. Liczby mają format „1.0.1” lub „1.2.5”. Jak mogę porównać te numery wersji? Myślę, że najpierw należy je sformatować jako ciąg, nie? Jakie opcje są potrzebne, aby określić, że wersja „1.2.5” pojawi się po wersji „1.0.1”?

mlecho
źródło
Napisałem tę małą bibliotekę, aby łatwo porównać 2 wersje Strings w Obj-C. Zwykle w iOS. Miej przykłady i kody na stronie GitHub
nembleton
3
Pomaga dokładnie wyjaśnić, czym jest schemat wersjonowania. Niektóre mogą mieć formaty wymagające dodatkowej logiki.
uchuugaka

Odpowiedzi:

244

To najprostszy sposób porównywania wersji, pamiętając, że „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
źródło
6
Używałem tej metody i ostatnio stwierdziłem, że zwraca (co uważam) błędne wyniki przy porównaniu np. 2.4.06 z 2.4.4. Uważam, że 2.4.06 powinno być niższe niż 2.4.4, ale może się mylę ... jakieś myśli?
Omer
7
@Omer: Dlaczego 06, a nie 6? Myślę, że większość programistów uznałaby 2.4.06 za wyższą wersję niż 2.4.4.
Stephen Melvin
4
To jest ładne i proste, ale opiera się na bardzo prostym schemacie wersji.
uchuugaka
11
@ScottBerrevoets Mam nadzieję, że nie tak to działa, ponieważ oznaczałoby to, że „1.2.3” jest mniejsze niż „1.1.12” (123 <1112)! Jak uważnie stwierdza Apple, „ Liczby w łańcuchach są porównywane za pomocą wartości liczbowych ”. Oznacza to, że każdy zestaw liczb w łańcuchach zostanie porównany (zasadniczo componentsSeparatedByStringpodejście). Możesz to sprawdzić samemu w @"1.8"vs @"1.7.2.3.55"i przekonać się, że 1.8 wychodzi na prowadzenie.
dooleyo
3
NSNumericSearch uważa, że ​​„1.0” jest mniejsze niż „1.0.0”. Nie dość elastyczny dla moich celów.
bobics
17

Dodam moją metodę, która porównuje wersje ściśle numeryczne (bez a, b, RC itp.) Z dowolną liczbą komponentów.

+ (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
źródło
13

To jest rozszerzenie do odpowiedzi Nathana de Vriesa w celu rozwiązania problemu 1 <1,0 <1,0,0 itd.

Po pierwsze, możemy rozwiązać problem z dodatkowymi „.0” w ciągu naszej wersji z NSStringkategorią:

@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

Dzięki powyższej NSStringkategorii możemy skrócić numery naszych wersji, aby usunąć niepotrzebne .0

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

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

Teraz możemy nadal stosować pięknie proste podejście zaproponowane przez Nathana de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
DonnaLea
źródło
To w połączeniu z rozwiązaniem Nathana de Vriesa jest najlepszą i najbardziej elegancką odpowiedzią.
Dalmazio
Czy to nadal nie mówi, że 7.4.2 jest nowszą wersją niż 7.5?
Tres
@Tres no. Ponieważ NSNumericSearch jest przekazywana jako opcja, ciągi są porównywane jako liczby, a zatem 7.4.2 <7.5
DonnaLea
9

Zrobiłem to sam, użyj kategorii.

Źródło..

@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;
    }
Piotr
źródło
Niesamowite! To jedyny przykład używający NSComparisonResult w tym wątku, który poprawnie porównuje 7.28.2 i 7.28.
CokePokes
8

Sparkle (najpopularniejsza platforma aktualizacji oprogramowania dla systemu MacOS) ma klasę SUStandardVersionComparator , która to robi, a także bierze pod uwagę numery kompilacji i znaczniki beta. To znaczy poprawnie porównuje 1.0.5 > 1.0.5b7lub 2.0 (2345) > 2.0 (2100). Kod używa tylko Foundation, więc powinien działać dobrze również na iOS.

bystrość
źródło
6

Sprawdź moją kategorię NSString, która implementuje łatwe sprawdzanie wersji na github; https://github.com/stijnster/NSString-compareToVersion

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

To zwróci wynik NSComparisonResult, który jest dokładniejszy niż użycie;

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

Dodaje się również pomocników;

[@"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
źródło
4

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

Wersja 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
źródło
4

Oto szybki kod 4.0 + do porównania wersji

 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
źródło
3

Pomyślałem, że po prostu podzielę się funkcją, którą wykorzystałem w tym celu. To wcale nie jest doskonałe. Proszę spojrzeć na przykłady i wyniki. Ale jeśli sprawdzasz własne numery wersji (co muszę zrobić, aby zarządzać takimi rzeczami, jak migracje baz danych), może to trochę pomóc.

(oczywiście usuń również instrukcje dziennika w metodzie. te są po to, aby pomóc ci zobaczyć, co robi)

Testy:

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

Wyniki:

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

Zauważ, że alfa działa, ale musisz być z nią bardzo ostrożny. kiedy w pewnym momencie przejdziesz do alfy, nie możesz jej rozszerzyć, zmieniając inne mniejsze liczby za nią.

Kod:

- (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
źródło
3

Moja biblioteka AppUpdateTracker na iOS zawiera kategorię NSString do wykonywania tego rodzaju porównań. (Wdrożenie opiera się na odpowiedzi DonnaLea .)

Sposób użycia byłby następujący:

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

Ponadto możesz go używać do śledzenia stanu instalacji / aktualizacji aplikacji:

[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);
}];
Szlagier
źródło
jest wersja 4.21 <4.3? if ([thisVersion isGreaterThanOrEqualToVersionString: @ "4.3"])
johndpope
Nie, 4,21 jest uważane za większe niż 4,3 jako 21> 3. Aby zapewnić zgodność z porównaniem równości, chciałbyś porównać 4,21 z 4,30. Proszę zobaczyć dyskusję w komentarzach do odpowiedzi Nathana de Vriesa .
Stunner
0

Glibc ma funkcję strverscmpi versionsort… niestety nie jest przenośny na iPhone'a, ale można dość łatwo napisać własne. Ta (nieprzetestowana) ponowna implementacja pochodzi z samego przeczytania udokumentowanego zachowania, a nie z czytania kodu źródłowego 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;
}
ephemient
źródło
2
to wygląda po prostu okropnie. Jedną z rzeczy, które najbardziej lubię w Objective-C, jest to, że przeważnie nie muszę już zajmować się zwykłym C.
Lukas Petr,
0

Jeśli wiesz, że każdy numer wersji będzie miał dokładnie 3 liczby całkowite oddzielone kropkami, możesz je przeanalizować (np. Używając sscanf(3)) i porównać:

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
źródło
0

Aby szybko sprawdzić wersję, możesz użyć następującego

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

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

Mam nadzieję, że to może być pomocne.

PatientC
źródło
0

Oto funkcja rekurencyjna, która działa z formatowaniem wielu wersji o dowolnej długości. Działa również dla @ „1.0” i @ „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);
    }

}

Próbki do badań:

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
źródło