Objective-C switch defines contents of string - objective-c

I've come from a java background, so i'm still getting to grips with some of the ways of doing things with Obj-C.
Depending on a number provided, I want an NSString variable to have different contents.
In java I would do something like this:
string foo;
switch (numberToSwtich){
case 1:
foo = "Something!";
break;
case 2:
foo = "Something Else!";
break;
}
Obviously there are two types of String in objective-c. NSString and NSSMutableString.
The difference being that you can change one at a later date. However, like java, can I initialize a NSString first then set its contents later or do I need to use an NSMutableString?
Something a bit like this...
NSString *aString = [[NSString alloc] init];
switch ([self getNumberOfSides]) {
case 1:
aString = #"A String";
break;
case 2:
aString = #"Another String";
break;
}
I'm aware there are other-ways of doing this, such as using an NSDictionary with numeric keys for example, but I would like to use a switch.
Thanks.

Your code is perfectly valid. There's no need to use NSMutableString.
Your code does leak memory, because you're not releasing the empty string you allocated using alloc. It's better to use the "string" class method for empty strings:
NSString *aString;
switch ([self getNumberOfSides]) {
case 1:
aString = #"A String";
break;
case 2:
aString = #"Another String";
break;
default:
aString = [NSString string];
break;
}

Related

What is the best way to initialise a variable in objective c, before a conditional statement?

I used to initialise variables before a conditional statement in the following way:
NSString *string = [[NSString alloc] init];
if (conditional statement) {
string = #"foo";
}
else{
string = #"bar";
}
But the Xcode Analyser complains:
"Value stored to 'string' during its initialization is never read"
So, I then tried a couple of different options:
A:
NSString *string = nil;
if (conditional statement) {
string = #"foo";
}
else{
string = #"bar";
}
B:
NSString *string = #"bar";
if (conditional statement) {
string = #"foo";
}
So my question is, what is the best way to initialise a variable in objective c, before a conditional statement?
UPDATE:
The variable itself is not used [read] in the conditional. Here is an example below...
NSString *string = [[NSString alloc] init];
if (x == 0) {
string = #"foo";
}
else{
string = #"bar";
}
UPDATE:
Based on Sven's answer, it seems like a good compromise is:
NSString *string;
if (x == 0) {
string = #"foo";
}
else{
string = #"bar";
}
A and B are both valid options. In the end it won't really matter, if you just assign string literals. The compiler might even generate the same code for both cases.
Of course if you assign something other than compile-time constants you need to be more careful. Depending on the side effects that happen in your computation only one or the other version will be correct.
In your case A you won't even have to nil-initialise the variable at first, the compiler is smart enough to see that it is initialised in any case. For patterns like this where you want to initialise an variable depending on some conditions it's actually a good idea to skip the initialisation where the variable is defined. Then the compiler can produce a warning if there is a code path where you forgot to initialise the variable.
Another option for this is to use the ternary operator ?::
NSString *string = condition ? #"foo" : #"bar";
This is not just shorter to write, but also makes it immediately clear that the variable is initialised no matter what the condition is.

Get a Objective-C method to return several NSStrings

I need to use a method that returns several strings, different ones, according to a value.
My code looks like:
- (void) returnStringsAccordingToSet:(NSString *)string1: (NSString *)string2: (NSInteger)setNo {
switch (setNo){
case 1:
if (generalStringSettings){
string1 = #"The first string";
string2 = #"The second string";
} else {
string1 = #"The first other string";
string2 = #"The second other string";
}
break;
case 2:
...
break;
case 3:
...
break;
}
}
I call that method with:
NSString *firstString = [[NSString alloc]init];
NSString *secondString = [[NSString alloc]init];
NSUInteger set = 1;
[self getStringsAccordingToSet: firstString: secondString: set];
I can't get it to work! All I get is empty strings. I've got a feeling that the call for the strings is somewhat wrong. Please help.
You can't mae it work because when you do
string1 = #"The first string";
you just override the local parameter and update its reference but nothing outside the callee is modified. So the value is changed just inside the scope of the function.
You should change the signature of the method to
- (NSArray*) returnStringsAccordingToSet:(NSString *)string1: (NSString *)string2: (NSInteger)setNo {
so that it returns a NSArray instead that nothing and then inside the function
case1:
return [NSArray arrayWithObjects:#"The first string",#"The second string",nil];
so you return the array and store it from the caller, then you can access the returned values.
NSArray *v = [returnStringAccordingTo: ..];
[v objectAtIndex:0]
Technically, since ObjectiveC is a superset of C, I guess it is possible to pass a pointer to a pointer to NSString by reference through (but this is discouraged in ObjC):
NSString *string = nil;
[self callMethod:&string];
-(void)callMethod:(NSString**)refToString {
*refToString = #"foobar";
You could do this by filling a NSArray, but just to show how C works I'll show the error that you made.
string1 and string2 are just pointers, so you can pass either mutable or immutable strings as arguments.But the original pointer never gets modified, for example:
NSString* string1=#"Hello"; // Let's suppose string1 is on the 0x8000 address
// And points to the 0x9000 address
NSString* string2=#"How are you?"; // string2: 0x1000 , pointing to 0x2000
[self returnStringsAccordingToSet: string1: string1 string2: string2];
Then when you call a method a copy is made for every pointer that you pass to the method:
- (void) returnStringsAccordingToSet:(NSString *)string1: (NSString *)string2: (NSInteger)setNo {
// string1 and string2 both point on the same address, but they are other pointers
// for example string1 is on 0x7000 and string2 on 0x7400, pointing to 0x9000
// and 0x2000
switch (setNo){
case 1:
if (generalStringSettings){
string1 = #"The first string"; // You just create another instance
string2 = #"The second string"; // of the string, but the original
// pointers aren't affected
} else {
string1 = #"The first other string";
string2 = #"The second other string";
}
break;
case 2:
...
break;
case 3:
...
break;
}
}
The solutions:
Pass a NSMutableString as argument and instead of assigning them, just modify the contents;
Pass a pointer to pointer;
Return a NSArray containing the new strings.
Update note: I personally don't use the method as a first thing to do. I use arrays if that's possible. Not sure if it's OK to use this with ARC. Some Apple methods use this approach to return error value, so it's good to be aware of it.
When method is called, those arguments you pass to function are copied (of course they are not getting the copy message, pointer is copied as far as you pass pointer), and those copies are used inside the function (or more technically initiated with the argument value). So if you change the copy (i.e. try to replace object on pointer, mutable objects are OK to modify) this will not reflect on the value. You may solve this using pointers (i.e. pass pointer to pointer).
So:
NSString* string = #"one string";
NSLog(string); // one string
// [self method:string]
- (void) method: (NSString*) string {
// Local copy created:
// NSString* string = copy of string (second pointer created, object not copied in memory)
string = #"other string";
NSLog(string); // other string
}
NSLog(string); // one string
You may do this like that:
- (void) method: (NSString**)str {
*str = #"Some string";
}
and then:
// string = "old vlalue"
NSString* string = #"old value";
// string = "Some string"
[self method:&string];

Compare id object and NSString

I cannot save the selected item from the array with a string value. Any idea what's wrong?
Its my code of compare:
if(self.detailItem){
[WebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[self.detailItem description] ofType:#"html"]isDirectory:NO]]];
NSString *value = (NSString *)self.detailItem;
NSString *value1 = (NSString *)#"DecideVectorVPiramidi";
value=[NSString stringWithFormat:#"%#", value];
value1=[NSString stringWithFormat:#"%#", value1];
if (value == value1) {
WebView.scalesPageToFit=YES;
}
This is the array code:
case 1:
switch (self.nomberInSection) {
case 0:
self.detailViewController.detailItem =[NSString stringWithFormat:#"%#", [DecideOfZakusPath objectAtIndex:indexPath.row]];
break;
}
break;
Type of detailItem is id.
For errors, I checked
You need to use isEqualToString. The ==operator will compare by reference only.
This means you have to do
if ( [value isEqualToString:value1] )
{
...
Your two strings will be pointers to two different memory locations, and the == will compare those two memory locations, and consequently evaluate to NO. The isEqualToString, or isEqual, is the method to use for doing a string comparison using the string equality rules (ie. if the two strings contains the same characters, they are considered equal).
This can be a bit counter-intuitive. In Objective-C, operator overloading is not possible. In many other languages, the operators can be overloaded so that a value comparison for built in and custom classes can be done using the equality operator.

Can Objective-C switch on NSString?

Is there a more intelligent way to rewrite this?
if ([cardName isEqualToString:#"Six"]) {
[self setValue:6];
} else if ([cardName isEqualToString:#"Seven"]) {
[self setValue:7];
} else if ([cardName isEqualToString:#"Eight"]) {
[self setValue:8];
} else if ([cardName isEqualToString:#"Nine"]) {
[self setValue:9];
}
Unfortunately they cannot. This is one of the best and most sought after utilizations of switch statements, so hopefully they hop on the (now) Java (and others) bandwagon!
If you are doing card names, perhaps assign each card object an integer value and switch on that. Or perhaps an enum, which is considered as a number and can therefore be switched upon.
e.g.
typedef enum{
Ace, Two, Three, Four, Five ... Jack, Queen, King
} CardType;
Done this way, Ace would be be equal to case 0, Two as case 1, etc.
You could set up a dictionary of blocks, like this:
NSString *lookup = #"Hearts"; // The value you want to switch on
typedef void (^CaseBlock)();
// Squint and this looks like a proper switch!
NSDictionary *d = #{
#"Diamonds":
^{
NSLog(#"Riches!");
},
#"Hearts":
^{
self.hearts++;
NSLog(#"Hearts!");
},
#"Clubs":
^{
NSLog(#"Late night coding > late night dancing");
},
#"Spades":
^{
NSLog(#"I'm digging it");
}
};
((CaseBlock)d[lookup])(); // invoke the correct block of code
To have a 'default' section, replace the last line with:
CaseBlock c = d[lookup];
if (c) c(); else { NSLog(#"Joker"); }
Hopefully Apple will teach 'switch' a few new tricks.
For me, a nice easy way:
NSString *theString = #"item3"; // The one we want to switch on
NSArray *items = #[#"item1", #"item2", #"item3"];
int item = [items indexOfObject:theString];
switch (item) {
case 0:
// Item 1
break;
case 1:
// Item 2
break;
case 2:
// Item 3
break;
default:
break;
}
Unfortunately, switch statements can only be used on primitive types. You do have a few options using collections, though.
Probably the best option would be to store each value as an entry in an NSDictionary.
NSDictionary *stringToNumber = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:6],#"Six",
[NSNumber numberWithInt:7],#"Seven",
[NSNumber numberWithInt:8],#"Eight",
[NSNumber numberWithInt:9],#"Nine",
nil];
NSNumber *number = [stringToNumber objectForKey:cardName];
if(number) [self setValue:[number intValue]];
A bit late but for anyone in the future I was able to get this to work for me
#define CASE(str) if ([__s__ isEqualToString:(str)])
#define SWITCH(s) for (NSString *__s__ = (s); ; )
#define DEFAULT
Here is the more intelligent way to write that. It's to use an NSNumberFormatter in the "spell-out style":
NSString *cardName = ...;
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[nf setNumberStyle:NSNumberFormatterSpellOutStyle];
NSNumber *n = [nf numberFromString:[cardName lowercaseString]];
[self setValue:[n intValue]];
[nf release];
Note that the number formatter wants the string to be lowercased, so we have to do that ourselves before passing it in to the formatter.
There are other ways to do that, but switch isn't one of them.
If you only have a few strings, as in your example, the code you have is fine. If you have many cases, you could store the strings as keys in a dictionary and look up the corresponding value:
NSDictionary *cases = #{#"Six" : #6,
#"Seven" : #7,
//...
};
NSNumber *value = [cases objectForKey:cardName];
if (value != nil) {
[self setValue:[value intValue]];
}
BY FAR.. my FAVORITE "ObjC Add-On" is ObjectMatcher
objswitch(someObject)
objcase(#"one") { // Nesting works.
objswitch(#"b")
objcase(#"a") printf("one/a");
objcase(#"b") printf("one/b");
endswitch // Any code can go here, including break/continue/return.
}
objcase(#"two") printf("It's TWO."); // Can omit braces.
objcase(#"three", // Can have multiple values in one case.
nil, // nil can be a "case" value.
[self self], // "Case" values don't have to be constants.
#"tres", #"trois") { printf("It's a THREE."); }
defaultcase printf("None of the above."); // Optional default must be at end.
endswitch
AND it works with non-strings, TOO... in loops, even!
for (id ifNumericWhatIsIt in #[#99, #0, #"shnitzel"])
objswitch(ifNumericWhatIsIt)
objkind(NSNumber) printf("It's a NUMBER.... ");
objswitch([ifNumericWhatIsIt stringValue])
objcase(#"3") printf("It's THREE.\n");
objcase(#"99") printf("It's NINETY-NINE.\n");
defaultcase printf("some other Number.\n");
endswitch
defaultcase printf("It's something else entirely.\n");
endswitch
It's a NUMBER.... It's NINETY-NINE.
It's a NUMBER.... some other Number.
It's something else entirely.
Best of all, there are SO few {...}'s, :'s, and ()'s
Objective-c is no different from c in this aspect, it can only switch on what c can (and the preproc def's like NSInteger, NSUInteger, since they ultimately are just typedef'd to an integral type).
Wikipedia:
c syntax:
The switch statement causes control to be transferred to one of several statements depending on the value of an expression, which must have integral type.
Integral Types:
In computer science, an integer is a datum of integral data type, a
data type which represents some finite subset of the mathematical
integers. Integral data types may be of different sizes and may or may
not be allowed to contain negative values.
I'm kind of late to the party, but to answer the question as stated, there's a more intelligent way:
NSInteger index = [#[#"Six", #"Seven", #"Eight", #"Nine"] indexOfObject:cardName];
if (index != NSNotFound) [self setValue: index + 6];
Note that indexOfObject will look for the match using isEqual:, exactly as in the question.
Building on #Graham Perks idea posted earlier, designed a simple class to make switching on strings fairly simple and clean.
#interface Switcher : NSObject
+ (void)switchOnString:(NSString *)tString
using:(NSDictionary<NSString *, CaseBlock> *)tCases
withDefault:(CaseBlock)tDefaultBlock;
#end
#implementation Switcher
+ (void)switchOnString:(NSString *)tString
using:(NSDictionary<NSString *, CaseBlock> *)tCases
withDefault:(CaseBlock)tDefaultBlock
{
CaseBlock blockToExecute = tCases[tString];
if (blockToExecute) {
blockToExecute();
} else {
tDefaultBlock();
}
}
#end
You would use it like this:
[Switcher switchOnString:someString
using:#{
#"Spades":
^{
NSLog(#"Spades block");
},
#"Hearts":
^{
NSLog(#"Hearts block");
},
#"Clubs":
^{
NSLog(#"Clubs block");
},
#"Diamonds":
^{
NSLog(#"Diamonds block");
}
} withDefault:
^{
NSLog(#"Default block");
}
];
The correct block will execute according to the string.
Gist for this solution
You can use macros approach to achieve it:
#define CASE(str) if ([__s__ isEqualToString:(str)])
#define SWITCH(s) for (NSString *__s__ = (s); ; )
#define DEFAULT
SWITCH (string) {
CASE (#"TestString") {
break;
}
CASE (#"YetAnotherString") {
break;
}
CASE (#"Test") {
break;
}
DEFAULT {
break;
}
}
I can't Comment on cris's answer on #Cris answer but i would like to say that:
There is an LIMITATION for #cris's method:
typedef enum will not take alphanumeric values
typedef enum
{
12Ace, 23Two, 23Three, 23Four, F22ive ... Jack, Queen, King
} CardType;
So here is another One:
Link Stack over flow Go to this user answer "user1717750"
typedef enum
{
Six,
Seven,
Eight
} cardName;
- (void) switchcardName:(NSString *) param {
switch([[cases objectForKey:param] intValue]) {
case Six:
NSLog(#"Six");
break;
case Seven:
NSLog(#"Seven");
break;
case Eight:
NSLog(#"Eight");
break;
default:
NSLog(#"Default");
break;
}
}
Enjoy Coding.....

NSString search for substring/ csv File IO

This has given me quite a big headache. For whatever reason, when I use this code, the if statement always evaluates to false:
while(!feof(file))
{
NSString *line = [self readNSString:file];
NSLog(#"%#", line);
NSLog(#"%#", search);
NSRange textRange;
textRange =[line rangeOfString:search];
if(textRange.location != NSNotFound)
{
NSString *result = [line substringFromIndex:NSMaxRange([line rangeOfString:search])];
resultView.text = result;
}
else
{
resultView.text = #"Not found";
}
}
When the functions execute, the two NSLogs tell me that the "line" and "search" strings are what they should be, so then why does the if statement always evaluate to false? I must be missing something simple, having another set of eyes would be great. Thanks
edit: (function "readNSString")
- (NSString*)readNSString:(FILE*) file
{
char buffer[300];
NSMutableString *result = [NSMutableString stringWithCapacity:256];
int read;
do
{
if(fscanf(file, "%299[^\n]%n%*c", buffer, &read) == 1)
[result appendFormat:#"%s", buffer];
else
break;
} while(r == 299);
return result;
}
edit 2:
search is set with a call to the first function, with an NSString* variable as a parameter, like this:
NSString *textFieldText = [[NSString alloc]
initWithFormat:#"%#", textField.text];
[self readFile:textFieldText];
edit 3 (NSLogs output)
line: Germany Italy France
search: Italy
I think that you are using the rangeOfString and the NSNotFound etc. correctly, so the problem is possibly to do with the creation of the string from the data read from the file using the appendFormat:#"%s".
I suspect there may be an encoding issue between your two string formats - I would investigate whether the "%s" encodes the null terminated C string properly into the same format as a unicode NSString with the appropriate encoding.
Try hard coding the value you are getting from the readNSString function as a string literal in code just for testing and see if that comparison works, if so this would tend to indicate it probably is something to do with the encoding of the string created from the file.