I've been trying to find a better way to switch on each character of a string.
My existing code is:
NSUInteger len = [oldName length], i;
SEL xSelector = #selector(characterAtIndex:);
unichar (*charAtIdx)(id, SEL, NSUInteger) = (typeof (charAtIdx)) [oldName methodForSelector:xSelector];
NSMutableString *NewName = [NSMutableString new];
for (i=0 ; i<len ; i++){
unichar c = charAtIdx(oldName,xSelector,i);
if (c == "Ú" || c == "°"){
[NewName appendString:#"s"];
}
else if (c == "Û" || c == "”"){
[NewName appendString:#"s"];
}
else if (c == "◊" || c == "˜"){
[NewName appendString:#"x"];
}
else blablabla
}
return NewName;
Now, the above seems to be working, however i have about 50 if statements that "switch" mainly extended ASCII codes (character codes 128-255) to more meaningful ones.
I thought about using a switch statement with a typedef enum and switch on that, however, the below doesn't work:
typedef enum {·,¡,Ê,∆} ExtendedASCII;
The idea would be to replace "unichar c = charAtIdx(oldName,xSelector,i);" with the below:
ExtendedASCII c = charAtIdx(oldName,xSelector,i);
Switch c
case 0: //being ·
case 1: // being ¡
blablabla
Any ideas????
thanks,
alex
usualy you could do
switch(c)
{
case:'a':; // fall throught
case:'b':
{
[NewName appendString:#"x"];
break;
}
}
but if you use 'Ú' you will get a compiler error, because this "char" is no unichar.
you can try to set the int value for the char
switch(c)
{
case: 218:; // 'Ú' , fall throught
case: 186: // '°'
{
[NewName appendString:#"x"];
break;
}
...
}
I cannot test, because I cannot get a valid 'Ú'.
Hoping for comments, maybe I can extend and answer upcomming questions ;)
Related
I am starting to learn Swift and hope to find it an excellent replacement for Objective C.
I am attempting to convert my Objective C classes into Swift and I cannot find the best way to translate the following method into Swift.
#implementation VersionReader
- (NSString *)readVersionFromString:(NSString *)string {
if (string.length == 0) {
return nil;
}
unichar firstChar = [string characterAtIndex:0];
if (firstChar < '0' || firstChar > '9') {
return nil;
}
NSUInteger length = string.length;
for (NSUInteger i = 0; i < length; ++i) {
if ([string characterAtIndex:i] == ' ') {
return [string substringToIndex:i];
}
}
return string;
}
#end
So far my Swift code looks like this:
import Cocoa
class VersionReader {
func readVersionFromString(string: String) -> String? {
if (string.isEmpty) {
return nil
}
var firstChar = string.characterAtIndex[0]
if (firstChar < 48 || firstChar > 57) {
return nil
}
var length = string.utf16Count
for (var i = 0; i < length; ++i) {
if (string.characterAtIndex(i) == 32) {
return string.substringToIndex(i)
}
}
return string
}
}
Tom this, I get the same error on two lines:
'String' does not have a member named 'characterAtIndex'
What would be an alternative to make this work in Swift? Thanks in advance.
A possible Swift solution:
func readVersionFromString(string: String) -> String? {
if string.isEmpty {
return nil
}
let firstChar = string[string.startIndex]
if !find("0123456789", firstChar) {
return nil
} else if let pos = find(string, " ") {
return string.substringToIndex(pos)
} else {
return string
}
}
Swift's String type doesn't have a characterAtIndex method. You can cast it to an NSString and use it as below:
var firstChar = (string as NSString).characterAtIndex(0)
Or
var firstChar = string.bridgeToObjectiveC().characterAtIndex(0)
Note that the characterAtIndex method returns an unichar, which seems to be what you want. But the correct approach to get a Swift Character would be that suggested by FreeAsInBeer in his answer: Array(string)[0]
Swift doesn't have a characterAtIndex selector. Instead, you need to use Array(string)[0].
please help me with this problem.
I want to check if the targetString match the keyword or not. Consider some character may different, but should still return true.
Example:
targetString = #"#ß<"
keyword = #"abc", #"∂B(", #"#Aß<"
result: all must return true.
(Matched.targetString and all keyword are the same.)
Consider me have an array, contains list of character set that can be the same:
NSArray *variants = [NSArray arrayWithObjects:#"aA#∂", #"bBß", #"c©C<(", nil]
So that when matching, with this rule, it can match as the example above.
Here is what i've done so far (using recursion):
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
char c = [keyWord characterAtIndex:i];
NSString *rs = [self variantsWithChar:c];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:[NSString stringWithCharacters:[aString characterAtIndex:i] length:1]].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
- (NSString *) variantsWithChar:(char) c {
for (NSString *s in self.variants) {
if ([s rangeOfString:[NSString stringWithFormat:#"%c",c]].location != NSNotFound) {
return s;
}
}
return [NSString stringWithFormat:#"%c", c];
}
The main problem is, variantsWithChar: doesn't return the correct string. I don't know which datatype and which function should I use here. Please help.
For thou who know ruby, here's the example in ruby. It work super fine!
require 'test/unit/assertions'
include Test::Unit::Assertions
class String
def matching?(keyword)
length >= keyword.length && (keyword.chars.zip(chars).all? { |cs| variants(cs[0]).include?(cs[1]) } || slice(1, length - 1).matching?(keyword))
end
private
VARIANTS = ["aA#∂", "bBß", "c©C<("]
def variants(c)
VARIANTS.find { |cs| cs.include?(c) } || c
end
end
assert "abc".matching?("#ß<")
PS: The fact is, it's containt a japanese character set that sounds the same (like あア, いイ... for thou who know japanese)
PS 2: Please feel free to edit this Question, since my engrish is sooo bad. I may not tell all my thought.
PS 3: And, maybe some may comment about the performance. Like, search about 10,000 target words, with nearly 100 variants, each variant have at most 4 more same characters.
So first off, ignore comments about ASCII and stop using char. NSString and CFString use unichar
If what you really want to do is transpose hiragana and katakana you can do that with CFStringTransform()
It wraps the ICU libraries included in OS X and iOS.
It makes it very simple.
Search for that function and you will find examples of how to use it.
After a while (a day) working on the code above, I finally get it through. But don't know about the performance. Someone comment and help me improve about performance, please. Thanks.
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
NSString* c = [NSString stringWithFormat:#"%C", [keyWord characterAtIndex:i]];
NSString *rs = [self variantsWithChar:c];
NSString *theTargetChar = [NSString stringWithFormat:#"%C", [aString characterAtIndex:i]];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:theTargetChar].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
If you remove all comment, it'll be pretty short...
////////////////////////////////////////
- (NSString *) variantsWithChar:(NSString *) c{
for (NSString *s in self.variants) {
if ([s rangeOfString:c].location != NSNotFound) {
return s;
}
}
return c;
}
You could try comparing ascii values of the japanese characters in the variants's each character's ascii value. These japanese characters aren't treated like usual characters or string. Hence, string functions like rangeOfString won't work on them.
to be more precise: have a look at the following code.
it will search for "∂" in the string "aA#∂"
NSString *string = #"aA#∂";
NSMutableSet *listOfAsciiValuesOfString = [self getListOfAsciiValuesForString:string]; //method definition given below
NSString *charToSearch = #"∂";
NSNumber *ascii = [NSNumber numberWithInt:[charToSearch characterAtIndex:0]];
int countBeforeAdding = [listOfAsciiValuesOfString count],countAfterAdding = 0;
[listOfAsciiValuesOfString addObject:ascii];
countAfterAdding = [listOfAsciiValuesOfString count];
if(countAfterAdding == countBeforeAdding){ //element found
NSLog(#"element exists"); //return string
}else{
NSLog(#"Doesnt exists"); //return char
}
===================================
-(NSMutableSet*)getListOfAsciiValuesForString:(NSString*)string{
NSMutableSet *set = [[NSMutableSet alloc] init];
for(int i=0;i<[string length];i++){
NSNumber *ascii = [NSNumber numberWithInt:[string characterAtIndex:i]];
[set addObject:ascii];
}
return set;
}
I'm looking for some code to do a general purpose equality comparison of arbitrary C types as supported by Objective-C's #encode() directive. I'm essentially looking for a function like:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char* bEnc)
Which you might call like this:
struct { ... } foo = { yadda, yadda, yadda };
struct { ... } bar = { yadda, yadda, yadda };
BOOL isEqual = CTypesEqual(&foo, #encode(typeof(foo)), &bar, #encode(typeof(bar)));
Here's what I've discovered so far:
Revelation #1
You can't do this:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
NSUInteger size = 0, align = 0;
NSGetSizeAndAlignment(aEnc, &size, &align);
if (0 != memcmp(a, b, size))
return NO;
return YES;
}
...because of garbage in the spaces between members created by alignment restrictions. For instance, the following will fail the memcmp based equality check, despite the two structs being equal for my purposes:
typedef struct {
char first;
NSUInteger second;
} FooType;
FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));
a.first = 'a';
a.second = ~0;
b.first = 'a';
b.second = ~0;
Revelation #2
You can abuse NSCoder to do this, like so:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
NSMutableData* aData = [[NSMutableData alloc] init];
NSArchiver* aArchiver = [[NSArchiver alloc] initForWritingWithMutableData: aData];
[aArchiver encodeValueOfObjCType: aEnc at: a];
NSMutableData* bData = [[NSMutableData alloc] init];
NSArchiver* bArchiver = [[NSArchiver alloc] initForWritingWithMutableData: bData];
[bArchiver encodeValueOfObjCType: bEnc at: b];
return [aData isEqual: bData];
}
That's great and all, and provides the expected results, but results in who knows how many heap allocations (at least 6) and makes an operation that should be relatively cheap, very expensive.
Revelation #3
You can't use NSValue for this. As in, the following does not work:
typedef struct {
char first;
NSUInteger second;
} FooType;
FooType a, b;
memset(&a, 0x55555555, sizeof(FooType));
memset(&b, 0xAAAAAAAA, sizeof(FooType));
a.first = 'a';
a.second = 0xFFFFFFFFFFFFFFFF;
b.first = 'a';
b.second = 0xFFFFFFFFFFFFFFFF;
NSValue* aVal = [NSValue valueWithBytes: &a objCType: #encode(typeof(a))];
NSValue* bVal = [NSValue valueWithBytes: &b objCType: #encode(typeof(b))];
BOOL isEqual = [aVal isEqual: bVal];
Revelation #4
Cocotron's NSCoder implementation punts on all the hard stuff (arbitrary structs, unions, etc.), thus is no source of further inspiration.
My attempt so far
So I started in on this, docs in hand, and I roughly got this far:
BOOL CTypesEqual(void* a, const char* aEnc, void* b, const char * bEnc)
{
if (0 != strcmp(aEnc, bEnc)) // different types
return NO;
return SameEncCTypesEqual(a, b, aEnc);
}
static BOOL SameEncCTypesEqual(void* a, void* b, const char* enc)
{
switch (enc[0])
{
case 'v':
{
// Not sure this can happen, but...
return YES;
}
case 'B':
case 'c':
case 'C':
case 's':
case 'S':
case 'i':
case 'I':
case 'l':
case 'L':
case 'q':
case 'Q':
case 'f':
case 'd':
case '#':
case '#':
{
NSUInteger size = 0, align = 0;
NSGetSizeAndAlignment(enc, &size, &align);
const int result = memcmp(a, b, size);
if (result)
return NO;
break;
}
case ':':
{
if (!sel_isEqual(*(SEL*)a, *(SEL*)b))
return NO;
}
case '*':
{
if (strcmp((const char *)a, (const char *)b))
return NO;
}
case '{':
{
// Get past the name
for (const char *prev = enc - 1, *orig = enc; prev < orig || (prev[0] != '=' && prev[0] != '\0' && enc[0] != '}'); prev++, enc++);
// Chew through it
for (NSUInteger pos = 0, size = 0, align = 0; enc[0] != '}' && enc[0] != '\0'; enc++, pos += size, size = 0, align = 0)
{
NSGetSizeAndAlignment(enc, &size, &align);
// figure out where we should be w/r/t alignment
pos = align * (pos + align - 1) / align;
// Descend
BOOL sub = SameEncCTypesEqual(((uint8_t*)a) + pos, ((uint8_t*)b) + pos, enc);
if (!sub)
return NO;
}
break;
}
case '[':
{
// Skip the '['
enc++;
// Get numElements
int numElements = 0;
sscanf(enc, "%d", &numElements);
// Advance past the number
for (; enc[0] <= '9' && enc[0] >= '0'; enc++);
// Get the size
NSUInteger size = 0, align = 0;
const char * const elementType = enc;
NSGetSizeAndAlignment(elementType, &size, &align);
for (NSUInteger i = 0; i < numElements; i++)
{
BOOL elementEqual = SameEncCTypesEqual(((uint8_t*)a) + i * size, ((uint8_t*)b) + i * size, elementType);
if (!elementEqual)
return NO;
}
break;
}
case '(':
{
NSLog(#"unions?! seriously, bro?");
return NO;
break;
}
default:
{
NSLog(#"Unknown type: %s", enc);
return NO;
break;
}
}
return YES;
}
...and about when I got to unions, I said to myself, "Self, why are you doing this? This is exactly the sort of code with a million little corner cases to miss, and really, it seems like something that should have been written a bunch of times already, by other people with way more patience." So here I am. Anyone know of a tried-and-true implementation of this in the (public) frameworks or "in the wild" that doesn't come with all the extra weight of using NSCoder?
Why not just make a function that skips the padding?
First, you would need to guess the padding policy. That's the easy part.
Then you use the encoding information provided by #encode to map the data types to masks, and use those masks to compare.
A trivial example:
struct m { char a; int b; };
struct n { char c; struct m d; int e; };
Could be transformed into (lets assume sizeof(int) is 4):
struct_m_mask = { 1, 4, 0 };
struct_n_mask = { 1, 1, 4, 4, 0 };
Optimization of the representation is of course possible in the case the alignment allows, e.g.:
struct_b_mask = { 2, 4, 4, 0 };
Then, you could walk this array to do the comparisons. A[n+1] - A[n] gives the hole size, if there's no hole ahead, like in the case of (b, e), then you can merge them.
That's the simplest way I could come up with. Probably you can implement more complex tricks.
BTW, my guess is that given the stuff is constant, the compiler could compute the masks at compile-time, but perhaps that's too much asking...
Regarding the padding issue you may have to make something like a function for each struct that turns it into an array for comparison or returns a single value at an index. It isn't very optimal but that is usually why sorting algorithms let you pass in function pointers that do the actual comparison.
I need to implement a method, which compares two strings for equality, considering some turkish letters as latin(e.g. ı = i). That's bottleneck in program, so it needs to be implemented as efficient as possible.
I can't use NSString compare: withOption:nsdiactricinsensitivesearch, because it doesn't work with turkish letters correctly.
Here's the implementation of my algorithm:
- (NSComparisonResult) compareTurkishSymbol:(unichar)ch with:(unichar)another
{
//needs to be implemented
//code like: if (ch == 'ı') doesn't work correctly
}
- (NSComparisonResult)compareTurkish:(NSString*)word with:(NSString*)another
{
NSUInteger i;
for (i =0; i < word.length; ++i) {
NSComparisonResult result =[self compareTurkishSymbol:[word characterAtIndex:i] with:[another characterAtIndex:i]];
if (result != NSOrderedSame) {
return result;
}
}
return another.length > word.length ? NSOrderedDescending : NSOrderedSame;
}
The problem is I can't compare unichars correctly. It doesn't compare correctly non-ascii symbols. How to deal with that?
Finally I found an answer.
unichar is unsigned short, that means every symbol has its code. So we can compare them not as chars but as numbers.
- (NSComparisonResult) compareTurkishSymbol:(unichar)ch with:(unichar)another
{
if (ch == 305) {//code of 'ı'
ch = 'i';
}
return ch - another;
}
I am writing an application that receives data with items and version numbers. The numbers are formatted like "1.0.1" or "1.2.5". How can I compare these version numbers? I think they have to be formatted as a string first, no? What options do I have to determine that "1.2.5" comes after "1.0.1"?
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
}
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
}
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;
}
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.
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"];
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")
}
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;
}
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);
}];
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")
}
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;
}
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
}
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.
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");
Based on #nathan-de-vries 's answer, I wrote SemanticVersion.swift for comparing Semantic Version, and here is the test cases.