比较 Objective-C 中的版本号

我正在编写一个应用程序,它接收带有项目和版本号的数据。这些数字的格式类似于“1.0.1”或“1.2.5”。如何比较这些版本号?我认为它们必须首先被格式化为字符串,不是吗?要确定“1.2.5”在“1.0.1”之后,我必须有哪些选项?

28646 次浏览

If you know each version number will have exactly 3 integers separated by dots, you can parse them (e.g. using sscanf(3)) and compare them:

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
}

Glibc has a function strverscmp and versionsort… unfortunately, not portable to the iPhone, but you can write your own fairly easily. This (untested) re-implementation comes from just reading the documented behavior, and not from reading Glibc's source code.

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

This is the simplest way to compare versions, keeping in mind that "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
}

Sparkle (the most popular software update framework for MacOS) has a SUStandardVersionComparator class that does this, and also takes into account build numbers and beta markers. I.e. it correctly compares 1.0.5 > 1.0.5b7 or 2.0 (2345) > 2.0 (2100). The code only uses Foundation, so should work fine on iOS as well.

I thought I'd just share a function I pulled together for this. It is not perfect at all. Please take a look that the examples and results. But if you are checking your own version numbers (which I have to do to manage things like database migrations) then this may help a little.

(also, remove the log statements in the method, of course. those are there to help you see what it does is all)

Tests:

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

Results:

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

notice that alpha works but you have to be very careful with it. once you go alpha at some point you cannot extend that by changing any other minor numbers behind it.

Code:

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

I'll add my method, which compares strictly numeric versions (no a, b, RC etc.) with any number of components.

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

This is an expansion to Nathan de Vries answer to address the problem of 1 < 1.0 < 1.0.0 etc.

First off we can address the problem of extra ".0"'s on our version string with an NSString category:

@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

With the above NSString category we can shorten our version numbers to drop the unnecessary .0's

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


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

Now we can still use the beautifully simple approach proposed by Nathan de Vries:

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

Check out my NSString category that implements easy version checking on github; https://github.com/stijnster/NSString-compareToVersion

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

This will return a NSComparisonResult which is more accurate then using;

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

Helpers are also added;

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

I made it myself,use Category..

Source..

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

My iOS library AppUpdateTracker contains an NSString category to perform this sort of comparison. (Implementation is based off DonnaLea's answer.)

Usage would be as follows:

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

Additionally, you can use it to keep track of your app's installation/update status:

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

To check the version in swift you can use following

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


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

Hope it might be helpful.

Swift 2.2 Version :

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 Version :

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

Here is a recursive function that do the works with multiple version formatting of any length. It also works for @"1.0" and @"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);
}


}

Test samples :

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

Here is the swift 4.0 + code for version comparison

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

Based on @nathan-de-vries 's answer, I wrote SemanticVersion.swift for comparing Semantic Version, and here is the test cases.