UTF8String problem - objective-c

I am facing a strange problem with this UTF8String:
parentMode = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
…
if(parentMode != #"Sleep")
{
NSLog(#"%s", [parentMode UTF8String]);
}
My questions are:
Why do I have to do this conversion in order to log parentMode?
The log is printing Sleep. So how is that if is done anyway?

You can't compare strings using the normal relational operators, you must use:
if (![parentMode isEqualToString:#"Sleep"])
{
NSLog (#"%#", parentMode);
}
You may want to check that parentMode is not nil before using that method, however. You don't need to use the UTF8String method, you can log the string directly using the %# format specifier. If this is not working, then there is something very important that you are omitting from the code you provided.

To log the string, you can write:
NSLog(#"%#", parentMode);
Using the %# placeholder, there's not need to convert it back to UTF-8.
This probably also explains why the if statement works.
Update:
You should compare string with isEqualToString:
[parentMode isEqualToString: #"Sleep"]

If you are comparing the integers then you have to use the syntax whatever you have used in the post.But when you compare the strings use this.
if (![parentMode isEqualToString:#"Sleep"])
{
NSLog (#"%#", parentMode);
}

Related

How to check if NSString format contains the same number of specifiers as there are variadic arguments?

To ensure that a formatted string returned by NSString initWithFormat:arguments: is as expected, I need to determine if there are the same number of format specifiers as arguments. Below is a (slightly contrived and highly edited) example:
- (void)thingsForStuff:(CustomStuff)stuff, ...
{
NSString *format;
switch (stuff)
{
case CustomStuffTwo:
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
format = #"Three things: %#, %#, and %#";
break;
default:
format = #"Just one thing: %#";
break;
}
va_list args;
va_start(args, method);
// Want to check if format has the same number of %#s as there are args, but not sure how
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedStuff);
}
Using this method, [self thingsForStuff:CustomStuffTwo, #"Hello", #"World"] would log
"Two things: Hello and World"
...but [self thingsForStuff:CustomStuffTwo, #"Hello"] would log
"Two things: Hello and "
...something that would be preferred to be caught before it happens.
Is there a way to count the format specifiers in a string, preferably something lightweight/inexpensive?
Well, I created my own regex, I have no idea if it's going to catch all of them, and may end finding some false positives, but seems to be working for me:
static NSString *const kStringFormatSpecifiers =
#"%(?:\\d+\\$)?[+-]?(?:[lh]{0,2})(?:[qLztj])?(?:[ 0]|'.{1})?\\d*(?:\\.\\d+)?[#dDiuUxXoOfeEgGcCsSpaAFn]";
You can count the number of arguments using:
NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern: kStringFormatSpecifiers options:0 error:nil];
NSInteger numSpecifiers = [regEx numberOfMatchesInString: yourString options:0 range:NSMakeRange(0, yourString.length)];
Is there a way to count the format specifiers in a string, preferably
something lightweight/inexpensive?
Nope -- really isn't. At least, not if you want it to work across all possible format strings. You would have to duplicate the parser that is used by stringWithFormat:. I.e. don't try to validate everything.
You could count the number of %, but that would not catch things like %% or other special cases. That may be good enough for your purposes.
Because of the way C and Objective-C handle variadic functions/methods like yours, you cannot in general tell how many arguments the user has provided.
Here are two ways to handle your situation.
First, look for another way to do this. The number of arguments you pass to the method is determined at compile-time. So maybe instead of using a variadic method, you should just have three methods:
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2;
- (void)doStuff:(CustomStuff)stuff withThing:(Thing *)thing1 thing:(Thing *)thing2 hatWearer:(Cat *)cat;
And you select the right method to call at compile-time based on how many arguments you want to pass, eliminating the switch statement entirely.
Second, I see that your predefined format strings only use the %# format. Does this mean that you expect the user to only pass objects to your method (aside from the (CustomStuff)stuff argument)?
If the user will only pass objects to your method, and you require those arguments to be non-nil, then you can get the compiler to help you out. Change your method to require the user to pass nil at the end of the argument list. You can tell the compiler that the argument list has to be nil-terminated by declaring the method (in your #interface) like this:
#interface MyObject : NSObject
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_REQUIRES_NIL_TERMINATION
#end
Now the compiler will warn the user “Missing sentinel in method dispatch” if he calls your method without putting a literal nil at the end of the argument list.
So, having changed your API to require some non-nil arguments followed by a nil argument, you can change your method to count up the non-nil arguments like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... {
int argCount = 0;
va_list args;
va_start(args, stuff);
while (va_arg(args, id)) {
++argCount;
}
va_end(args)
int expectedArgCount;
NSString *format;
switch (stuff) {
case CustomStuffTwo:
expectedArgCount = 2;
format = #"Two things: %# and %#";
break;
case CustomStuffThree:
expectedArgCount = 3;
format = #"Three things: %#, %#, and %#";
break;
// etc.
}
NSAssert(argCount == expectedArgCount, #"%# %s called with %d non-nil arguments, but I expected %d", self, (char*)_cmd, argCount, expectedArgCount);
va_start(args, stuff);
NSString *formattedStuff = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSLog(#"Things: %#", formattedString);
}
You could count the number of format specifiers, but IIRC you will never be able to count the number of arguments passed into a variable-argument method. This is because of the way C pushes arguments on the stack without specifying how many it has pushed.
Most functions overcome this by requiring that the last argument be nil or some kind of terminator (see [NSArray arrayWithObjects:]). There's even a macro that allows the compiler to check this and emit a warning at compile time.
You can use NS_FORMAT_FUNCTION at the end of your function prototype, as in stringWithFormat method of NSString.
So your method's prototype should be like this:
- (void)thingsForStuff:(CustomStuff)stuff, ... NS_FORMAT_FUNCTION(1,2);
long specifierCount = [myFormatString componentsSeparatedByString:#"%"].count;
This will get you close. Its just a simple split. You would have to account for escaped % values.

Parsing json incorrect

i'm parsing something from the Apple JSON (the rating of the app) and i tried something like:
if ([StoreParse objectForKey:#"averageUserRating"] == #"4.5") {
NSLog(#"xx");
} else {
NSLog(#"xxx");
}
The App has a rating of 4.5 and if i do
NSlog (#"%#", [StoreParse objectForKey:#"averageUserRating"]);
The output is : 4.5
but when i run the script the NSlog in the first code's output is "xxx" does anyone can help me?
Comparing strings, which are essentially pointers to instances of the NSSring class, is erroneous, as two identical-content strings can have a different memory address. Use
if ([[StoreParse objectForKey:#"averageUserRating"] isEqualToString:#"4.5"])
instead.
Use isEqualToString:
if ([[StoreParse objectForKey:#"averageUserRating"] isEqualToString:#"4.5"]) {
NSLog(#"xx");
}
You can't do this:
if ([StoreParse objectForKey:#"averageUserRating"] == #"4.5")
You need to do:
if ([[StoreParse objectForKey:#"averageUserRating"] isEqualToString:#"4.5"])
That's assuming it's a string. If it's an NSNumber then do:
if ([[StoreParse objectForKey:#"averageUserRating"] floatValue] == 4.5f)
(Although be careful about comparing equality of floats)
See this question for more information on string equality.
If the value for averageUserRating is an NSNumber, you should convert it to a formatted NSString first then compare it to the #"4.5" string literal:
if ([[NSString stringWithFormat:#"%1.1f", [[StoreParse objectForKey:#"averageUserRating"] floatValue]] isEqualToString:#"4.5"])

How to check if [request responseString] is equal to some other string?

For example [request responseString]'s value sent by my servlet to my iphone application is "myinfo". In my iphone application, I made a string like this:
NSString *str = #"myinfo";
then i have if else
if([responseString isEqualToString:str]){
NSLog(#"condition true");
}else{
NSLog(#"condition false");
}
in console its always showing "condition false". Whats the problem? Isn't isEqualToString is write method to check if strings are equal or not? Thanks in advance.
Howwever much you think your two strings are completely equal, they are not. Believe me, if -isEqualToString: did not return YES for two equal strings, somebody would have noticed in the 20 odd years it has been part of the Cocoa API.
I suspect that one of your two strings contains some non printing characters. You might have a line feed or a space or a tab in it. Another possibility (one that I came across recently) is that, for certain character set encodings, you can create an NSString with a nul character in it. If it's at the end, it won't show up. Try logging the lengths of the two strings, or converting them to NSData objects using the UTF16 encoding and logging them.
The NSString method isEqualToString is the correct thing to use here. You can do a sanity check by adding a log to your method:
NSLog(#"responseString = %#",responseString);
NSLog(#"str = %#",str);
if([responseString isEqualToString:str]){
NSLog(#"condition true");
}else{
NSLog(#"condition false");
}
Remember that NSStrings are Case Sensitive, so the two strings must appear exactly the same.
Since you said you're using connectios, sometimes the data retrieved by web is weird, you should first encode the data in a string and then NSLog it to see if it has special characters.
NSString *response = [[NSString alloc] initWithData:dataRetrieved encoding:NSUTF8StringEncoding];
Then you can make sure if your [request responseString] is not getting special chars.
The better way to compare strings in ios:
NSString *responseString = <your string>;
NSString *string2 = <your string>;
if ([responseString caseInsensitiveCompare:string2] == NSOrderedSame) {
//strings are same
} else {
//strings are not same
}

String compare Objective-C

I've been struggling with a simple comparison but I can't get it to work.
I´m reading a XML file and I need to compare data from it in order to show the right picture.
http://www.cleaner.se/larm.xml (Example file for parsing)
I have tried things like:
if([aLarm.larmClass isEqualToString:#"A"])
NSLog(#"same");
else
NSLog(#"Not same");
If I use: NSLog(aLarm.larmClass); console puts it out nicely as it should. What am I doing wrong?
You can use the NSString compare: methods. For example:
if ([myString caseInsensitiveCompare:#"A"] == NSOrderedSame ) {
NSLog(#"The same");
} else {
NSLog(#"Not the same.");
}
The result is an NSComparisonResult which is just an enum with types NSOrderedSame, NSOrderedAscending and NSOrderedDescending.
Check the documentation on the various compare: methods here.
Of course, if the receiver is actually an NSString, then isEqualToString: should also work. So if you're trying to compare a class name (aLarm.larmClass ??), then you can call:
if ([NSStringFromClass([aLarm class]) isEqualToString:#"A"] ) {
NSLog(#"The same");
}
If the larmClass property is a string, make sure that it is actually one character in length (i.e. it doesn't have any leading or trailing whitespace that was accidentally included when parsing the XML). If the larmClass property truly is an NSString containing the letter ‘A’ then [aLarm.larmClass isEqualToString:#"A"] will return YES.
Do a:
NSLog(#"%u, %#", [aLarm.larmClass length], aLarm.larmClass);
and just make sure that it shows “1, A”.

NSLog incorrect encoding

I've got a problem with the following code:
NSString *strValue=#"你好";
char temp[200];
strcpy(temp, [strValue UTF8String]);
printf("%s", temp);
NSLog(#"%s", temp);
in the first line of the codes, two Chinese characters are double quoted. The problem is printf function can display the Chinese characters properly, but NSLog can't.
Thanks to all. I figured out a solution for this problem. Foundation uses UTF-16 by default, so in order to use NSLog to output the c string in the example, I have to use cStringUsingEncoding to get UTF-16 c string and use %S to replace %s.
NSString *strValue=#"你好";
char temp[200];
strcpy(temp, [strValue UTF8String]);
printf("%s", temp);
strcpy(temp, [strValue cStringUsingEncoding:NSUTF16LittleEndianStringEncoding]);
NSLog(#"%S", temp);
NSLog's %s format specifier is in the system encoding, which seems to always be MacRoman and not unicode, so it can only display characters in MacRoman encoding. Your best option with NSLog is just to use the native object format specifier %# and pass the NSString directly instead of converting it to a C String. If you only have a C string and you want to use NSLog to display a message instead of printf or asl, you will have to do something like Don suggests in order to convert the string to an NSString object first.
So, all of these should display the expected string:
NSString *str = #"你好";
const char *cstr = [str UTF8String];
NSLog(#"%#", str);
printf("%s\n", cstr);
NSLog(#"%#", [NSString stringWithUTF8String:cstr]);
If you do decide to use asl, note that while it accepts strings in UTF8 format and passes the correct encoding to the syslog daemon (so it will show up properly in the console), it encodes the string for visual encoding when displaying to the terminal or logging to a file handle, so non-ASCII values will be displayed as escaped character sequences.
My guess is that NSLog assumes a different encoding for 8-bit C-strings than UTF-8, and it may be one that doesn't support Chinese characters. Awkward as it is, you might try this:
NSLog(#"%#", [NSString stringWithCString: temp encoding: NSUTF8StringEncoding]);
I know you are probably looking for an answer that will help you understand what's going on.
But this is what you could do to solve your problem right now:
NSLog(#"%#", strValue);
# define NSLogUTF8(a,b) NSLog(a,[NSString stringWithCString:[[NSString stringWithFormat:#"%#",b] cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding])
#define NSLogUTF8Ex(a,b) NSLog(a,[MLTool utf8toNString:[NSString stringWithFormat:#"%#",b]])
+(NSString*)utf8toNString:(NSString*)str{
NSString* strT= [str stringByReplacingOccurrencesOfString:#"\\U" withString:#"\\u"];
//NSString *strT = [strTemp mutableCopy];
CFStringRef transform = CFSTR("Any-Hex/Java");
CFStringTransform((__bridge CFMutableStringRef)strT, NULL, transform, YES);
return strT;
}