Порівняйте номери версій у Objective-C


87

Я пишу заявку, яка отримує дані з елементами та номерами версій. Номери відформатовані як "1.0.1" або "1.2.5". Як я можу порівняти ці номери версій? Думаю, спочатку їх потрібно відформатувати як рядок, ні? Які варіанти я маю визначити, що "1.2.5" постає після "1.0.1"?


Я написав цю невелику бібліотеку, щоб легко порівняти 2 версії Strings в Obj-C. Зазвичай в iOS. Майте приклади та коди на сторінці GitHub
Nembleton

3
Це допомагає точно з’ясувати, що таке схема версій. Деякі можуть мати формати, що вимагають додаткової логіки.
uchuugaka

Відповіді:


244

Це найпростіший спосіб порівняння версій, маючи на увазі, що "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
Я використовував цей метод, і нещодавно виявив, що він повертає (те, що я вважаю) неправильні результати при порівнянні, тобто: 2.4.06 з 2.4.4. Я вважаю, що 2.4.06 має бути нижчим за 2.4.4, але, можливо, я помиляюся ... якісь думки?
Омер,

7
@Omer: Чому 06, а не 6? Думаю, більшість розробників вважають 2.4.06 версією вищою, ніж 2.4.4.
Стівен Мелвін,

4
Це приємно і просто, але спирається на дуже просту схему версій.
uchuugaka

11
@ScottBerrevoets Я б, звичайно, сподівався, що це не так, як це працює, оскільки це означало б, що "1.2.3" менше, ніж "1.1.12" (123 <1112)! Як Apple обережно заявляє, " цифри в рядках порівнюються з використанням числового значення ". Тобто порівнюватимуться набори чисел у рядках (по суті, componentsSeparatedByStringпідхід). Ви можете перевірити це самостійно за допомогою @"1.8"vs @"1.7.2.3.55"і побачити, що 1.8 виходить попереду.
dooleyo

3
NSNumericSearch вважає, що "1,0" менше, ніж "1,0,0". Не досить гнучкий для моїх цілей.
bobics

17

Я додаю свій метод, який порівнює строго числові версії (без a, b, RC тощо) з будь-якою кількістю компонентів.

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

Це розширення до відповіді Натана де Фріза для вирішення проблеми 1 <1,0 <1,0,0 тощо.

Спочатку ми можемо вирішити проблему зайвих ".0" у нашому рядку версії з NSStringкатегорією:

@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

За допомогою вищезазначеної NSStringкатегорії ми можемо скоротити номери версій, щоб скинути непотрібні .0

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

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

Тепер ми все ще можемо використовувати чудово простий підхід, запропонований Натаном де Фрізом:

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

Це в поєднанні з рішенням Nathan de Vries - найкраща та найелегантніша відповідь.
Далмаціо

Хіба це все одно не говорить, що 7.4.2 є вищою версією, ніж 7.5?
Трес

@Tres немає. Оскільки NSNumericSearch передається як опція, рядки порівнюються як числа, таким чином 7.4.2 <7.5
DonnaLea

9

Я зробив це сам, використовую категорію ..

Джерело ..

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

Тест ..

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

Чудово! Це єдиний приклад використання NSComparisonResult у цьому потоці, який правильно порівнює 7.28.2 та 7.28.
CokePokes

8

Sparkle (найпопулярніша програма оновлення програмного забезпечення для MacOS) має клас SUStandardVersionComparator, який робить це, а також враховує номери збірки та бета-маркери. Тобто це правильно порівнює 1.0.5 > 1.0.5b7або 2.0 (2345) > 2.0 (2100). Код використовує лише Foundation, тому він також повинен добре працювати на iOS.


6

Перегляньте мою категорію NSString, яка реалізує просту перевірку версій на github; https://github.com/stijnster/NSString-compareToVersion

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

Це поверне NSComparisonResult, який є більш точним, ніж використання;

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

Також додаються помічники;

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

Версія 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")
        }

Версія 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

Ось швидкий код 4.0 + для порівняння версій

 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

Я думав, що просто поділюся функцією, яку я зібрав для цього. Це зовсім не ідеально. Погляньте, будь ласка, на приклади та результати. Але якщо ви перевіряєте власні номери версій (що я повинен зробити для управління такими речами, як міграція баз даних), це може трохи допомогти.

(також, звичайно, видаліть в методі оператори журналу. вони там, щоб допомогти вам зрозуміти, що він робить - це все)

Тести:

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

Результати:

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

зверніть увагу, що альфа працює, але з цим потрібно бути дуже обережним. як тільки ви перейдете до альфа-версії в якийсь момент, ви не зможете продовжити це, змінивши будь-які інші незначні числа за ним.

Код:

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

Моя бібліотека iOS AppUpdateTracker містить категорію NSString, щоб виконати подібне порівняння. (Реалізація базується на відповіді DonnaLea .)

Використання буде таким:

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

Крім того, ви можете використовувати його для відстеження стану встановлення / оновлення вашого додатка:

[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 isGreaterThanOrEqualToVersionString: @ "4.3"])
johndpope

Ні, 4.21 вважається більшим за 4.3, оскільки 21> 3. Для того, щоб задовольнити ваше порівняння рівності, ви хочете порівняти 4.21 з 4.30. Див. Дискусію в коментарях до відповіді Натана де Фріза .
Stunner

0

Glibc має функцію strverscmpі versionsort... на жаль, не переноситься на iPhone, але ви можете написати свою власну досить легко. Ця (неперевірена) повторна реалізація походить від простого читання задокументованої поведінки, а не від читання вихідного коду 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
це виглядає просто жахливо. Одне з речей, яке мені найбільше подобається в Objective-C, це те, що здебільшого мені більше не доводиться мати справу з простим C.
Лукас Петр

0

Якщо ви знаєте, що в кожному номері версії буде рівно 3 цілі числа, розділені крапками, ви можете проаналізувати їх (наприклад, за допомогою sscanf(3)) та порівняти:

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

Щоб швидко перевірити версію, ви можете скористатися наступним

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

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

Сподіваюся, це може бути корисним.


0

Ось рекурсивна функція, яка виконує роботи з форматуванням декількох версій будь-якої довжини. Він також працює для @ "1.0" та @ "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);
    }

}

Тестові зразки:

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");
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.