Comparing Strings in Cocoa - objective-c

I have tried:
- (NSString*) generateString
{
NSString* stringToReturn = #"thisString";
return stringToReturn;
}
- (void) otherMethod
{
NSString *returnedString = [self generateString];
if (returnedString == #"thisString")
{ // Do this }
else if (returnedString == #"thatString")
{ // Do that }
}
Which never matches.
I have then tried
if ([returnedString compare:#"thisString"] == 1)
But the compare method always returns 1 for me, even when comparing with a different string.
What is the correct way to do this, and what result should I expect?

First of all, you are using the == operator to compare two object pointers (of type NSString *). So that returns true when the pointers are the same, not when the strings have the same contents. If you wanted to compare whether two strings are the same, you should use isEqualToString: or isEqual: (isEqual: is more general as it works for all types of objects).
Second, compare: returns 0 (NSOrderSame) when they are the same, and 1 (NSOrderedDescending) when the first is greater than the second. So in fact it returns 1 only when they are different (specifically, when the first is greater than the second).

[returnedString isEqualToString: #"thisString"]

When comparing two identical strings compare will return NSOrderedSame, which is 0. It can also return NSOrderedAscending, -1, and NSOrderedDescending, 1.
You may prefer to use isEqualToString which returns YES or NO.

if ([returnedString isEqualToString:#"thisString"])
NSLog(#"Equal");
else
NSLog(#"Not Equal");

The == operator when applied to objects check whether the pointers are equal.
You have to use -compare method or one of its companions -compare:options: -compare:options:range: or -compare:options:range:locale: which return a NSComparisonResult (NSOrderAscending, NSOrderSame, NSOrderDescending)
If you just need equality comparison, you can use -isEqualToString: which returns a BOOL value.

Related

Why a mutable copy of a NSString is said to be equal to the original immutable one?

Suppose I have a NSString* str1 and NSMutableString* str2, and I make str2 the mutable copy of str1. And I called the following method:
-(void)someMethod {
NSString *str1;
NSMutableString *str2;
str1 = #"OK";
str2 = [str1 mutableCopy];
if ([str2 isEqual:str1]) {
NSLog(#"Same!");
}
else {
NSLog(#"Not exactly!");
}
NSLog(#"%#", [[str1 class] description]);
NSLog(#"%#", [[str2 class] description]);
}
Console output:
2014-01-07 14:03:16.291 LearnFoundation[3739:303] Same!
2014-01-07 14:03:16.293 LearnFoundation[3739:303] __NSCFConstantString
2014-01-07 14:03:16.293 LearnFoundation[3739:303] __NSCFString
So here comes the confusion, according to the documentation of isEqual in NSString, it returns a Boolean value that indicates whether the receiver and a given object are equal. So why the mutable copy is said to be the same as the original immutable one?
Thanks in advance!
There are (at least) three separate concepts that can all be thought of as "equality":
"identity" (am I the same object as that other object?)
"equality" (am I exactly identical to this other object?)
"value equality" (do I have the same value as this other object?)
For ObjC objects you test for equal identities with ==, but equivalent values with isEqual:. There's no one-stop shop method for testing exact equality; it turns out to not be very useful, in general.
In Javascript (for comparison's sake), you test for equal identities with === and equivalent values with ==. There is similarly no direct way to test for exact equality.
For pass-by-value types like int and float, there's no such thing as identity, since you can't pass a particular instance around. However, if you squint a bit, you can think of this as being a similar case of different types with the same value:
int x = 5;
short y = 5;
if (x == y) {
...
}
Though in this case it's not a subtype relationship.
isEqual compares the contents of the two strings, not their types or identities. The contents are equal so it evaluates true.
To compare types, try:
if([str1 isKindOfClass:[str2 class]])
{
NSLog(#"same");
}else{
NSLog(#"different");
}
You should see "different" get logged.
They're equal as strings, but are distinct objects with distinct addresses. Thus, isEqual returns YES but a comparison with == would evaluate to NO.

if NSString does not equal function?

I have searched all over the place for this, including apple's docs on NSString (maybe I didn't see?) but I am trying to find a method in xCode for checking wether a NSString does not equal to something. Much like
if (myNSSting = #"text" {...
except specifically I want to check if it does not equal to 'text'.
if(![myNSString isEqualToString:#"text"])
For case insensitive compare we can handle by:
if( [#"Some String" caseInsensitiveCompare:#"some string"] == NSOrderedSame ) {
// strings are equal except for possibly case
}

Objective-C string comparison

I have an array of names but can't seem to make the comparison work. Do I have an improper use of the language here?
NSLog(#"%#",[arrayOfNames objectAtIndex:0]);
if ([arrayOfNames objectAtIndex:0] == "Blue"){
NSLog(#"it's Blue");
}
else {
NSLog(#"it's not Blue");
}
The output is the following one:
Blue
it's not Blue
Use the following:
if ([[arrayOfNames objectAtIndex:0] isEqualToString:#"Blue"])
You're comparing two objects (one of the id-type, the other is a C-string) with the == operator. The comparison will fail, since they are 2 different objects. With the isEqualToString you are comparing the value of the object to the string #"Blue".

convert id into enum using objective-c

I am trying to implement a simple method, however I am still quite a newbie on objective-c.
I have this simple method which is trying to convert from an id to a specific value in enum, if matched.
This is the enum
typedef enum {
DXTypeUnknown = 0,
DXDatasource = 1,
DXGroup = 2
} DXPropertyType;
And this is the relevant method:
-(DXPropertyType)typeFromObject:(id)_type {
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return [NSNumber numberWithInt:DXDatasource];
case DXGroup:
return [NSNumber numberWithInt:DXGroup];
default:
return [NSNumber numberWithInt:DXTypeUnknown];
}
}
The very first check I would to implement is if the id can be converted to an int, then see if it falls in the two relevant categories group or datasource, or return a default value if not. Could you tell me if the switch/case I implemented is a proper solution or not ?
I would like also this method not to causing crash of an application, so what could be advisable to check, keeping in mind that in any case the default value is to be returned.
thanks
[EDIT]
I forgot to say that this value is going to be stored in a field of a NSManagedObject, which by CoreData restriction can be an NSNumber, so probably there's a better solution instead of an enum.
It might be a good idea to include this code to check if the id can be used:
if (![_type respondsToSelector:#selector(intValue)])
return nil;
However, if you'll always pass a NSNumber go ahead and declare the method as:
- (DXPropertyType)typeFromObject:(NSNumber)_type;
In your code, you're returning a NSNumber. I don't think that's what you really
want, as you'd be doing nothing with the NSNumber passed. Return the enum
item:
-(DXPropertyType)typeFromObject:(id)_type {
if (![_type respondsToSelector:#selector(intValue)])
return nil;
int _t = [_type intValue];
switch (_t) {
case DXDatasource:
return DXDatasource;
case DXGroup:
return DXGroup;
default:
return DXTypeUnknown;
}
}
And then this can be simplified to:
- (DXPropertyType)typeFromObject:(id)_type {
if ([_type respondsToSelector:#selector(intValue)]) {
int t = [_type intValue];
DXPropertyType property_t;
if (t >= 1 && t <= 2)
property_t = t;
else
property_t = DXTypeUnknown;
return property_t;
}
return nil;
}
Your switch statement is a good solution and will not cause a crash.
However, your method returns a NSNumber when it expects a different return. I suggest changing the method to
-(NSNumber)typeFromObject:(id)_type
You specify that your method returns an enum, but you return objects. So either return the enum values or specify the return type to be NSNumber *.
A different solution could be using singleton objects instead of an enum, but that's probably more work than it's worth. Think [NSNull null].

Best way to implement a true/false/undefined trichotomy variable in objective-c

I'm temped to use an int, and make 0 == NO, 1 == YES, and anything else == undefined.
Obviously there are a million ways to do something like this, but what seems like the best way to you? Concerns I can think of include simplicity and memory footprint (e.g. what if I have a lot of these?).
Another way is to use two BOOLs, one for isDefined, and one for value
Another way,
typedef enum { CPStatusUndefined, CPStatusAvailable, CPStatusUnavailable } CPStatus;
Edit, the use case is:
I have a yes/no property that is difficult to calculate. When it is being checked, it is checked frequently (by UIMenuController, OFTEN), but unless the user selects it, it is never checked. The way I chose to deal with this is a tri-type variable. The first time you check, if it is undefined you calculate the yes/no value and return it, after that you just return the yes/no value.
Use an enum. In Objective-C they work just like they do in C/C++
typedef enum {
No = 0,
Yes,
Other
} tri_type;
tri_type myVar = No;
if( myVar == Yes || myVar == Other ) {
// whatever
}
How about NSNumber, since it can be nil?
[number boolValue] == YES;
[number boolValue] == NO;
[number boolValue] == nil; // or just number == nil
If you want to conserve the most amount of memory, use a char.
char == 0, false
char == 1, true
else, undefined.
Obviously, you'll want to initialize it at something like -1.
This is the way obj-c does comparator return values:
if 0, they are equal.
if positive, a > b
if negative, a < b
Same idea as above.