NSString and isEqualToString with strange output - objective-c

I got a error in my app and as I tried to fix it I had a really strange behavior when I debugged my method.
Check the following code:(testString is an NSString)
NSLog(#"logging:AAAA%#AAAA",[testObject testString]);
if ([[testObject testString] isEqualToString:#"(null)"]) {
NSLog(#"yeah im here!");
}
and its printing:
logging:AAAA(null)AAAA
But it never reaches the "yeah im here!". How is that possible?

The output of testString is nil.
NSLog produces "(null)" when passed a nil object. However calling a method (like isEqualToString) on a nil object evaluates to nil again and thus to false in the "if".

NSLog always prints the description of an Object.
In fact it's NS_FORMAT_FUNCTION.
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
So:
NSString *stringForNSLog = [NSString stringWithFormat:#"%#",[testObject testString]];
the *stringForNSLog should exactly be "(null)".
But the value of testString is nil.
If you're looking into GNUStep, an open-source implementation of Apple's Cocoa, you'll find something like this:
all the string format things are written in GSFormat.m
and in GSFormat.m
size_t len;
id obj;
NSString *dsc;
obj = args_value[specs[nspecs_done].data_arg].pa_object;
if (!obj) dsc = #"(null)";
else if ([obj respondsToSelector: #selector(descriptionWithLocale:)]) dsc = [obj descriptionWithLocale: locale];
else dsc = [obj description];
http://svn.gna.org/svn/gnustep/libs/base/trunk/Source/NSString.m
http://svn.gna.org/svn/gnustep/libs/base/trunk/Source/GSFormat.m

Because testString method returns nil and calling a method on nil does nothing.

NSLog prints the literal (null) if your NSString is nil. Change your test to:
if ([[testObject testString] == nil)

Because the testObject is nil itself.
You could test:
if ((testObject==nil)||([[testObject testString] isEqualToString:#"(null)"])) {
NSLog(#"yeah im here!");
}

if ((testObject==nil)||([[testObject testString] isEqualToString:#"(null)"])) {
NSLog(#"yeah im here!");
}

Related

comparing nil to integer in Objective-C

In the following case where string is an NSString
if (string.length < 1)
{
return;
}
and string turns out to be nil the if statement will still evaluate correctly because in this case nil evaluates to 0.
However, is this recommended practice (by Clang or Apple) and are there any arguments against this and doing something closer to:
if (!string || string.length < 1)
{
return;
}
It's very common to do something like:
if (string.length) {
// string is not nil and the string has a non-zero length
} else {
// either string is nil or the length is zero
}
There is no need, in such a case, to check to see if string is nil or not.
When string is nil, you end up doing [nil length]. Calling any method on a nil pointer results in a value of "zero". How the "zero" is interpreted depends on the method's return type.
Primitive types appear as 0. BOOL appears as NO. Pointers appear as nil.
I'd say it's fine. Your alternate line is harder to understand at a glance and relies on the same logic (nil == 0) as the original line.
Some cases,for example working with strings that are made from response of requests, you should check the string, is a string, and not data!
NSString *string;
if ([string isKindOfClass:[NSString class]]) {
//
}
Cheers!

NSNull isEqualToString Issue With JSON Response

I am getting the following error
-[NSNull isEqualToString:]: unrecognized selector sent to instance 0x3c168090
on this line of code
cell.offerTitle.text = [voucherData objectForKey:#"offer_title"];
Could someone help me correct the problem please?
Thanks
Oliver
Is voucherData an NSDictionary?
It's possible there's an NSNull in your dictionary, and when the dictionary is trying to find the object for offer_title, it's running into trouble.
Another possibility is that [voucherData objectForKey:#"offer_title"] is returning [NSNull null], and the label is barfing when you try to pass that instead of a string.
Try setting a breakpoint in objc_exception_throw and read the stack trace – that will give you a much better idea of what's going on.
Added:
id value = [voucherData objectForKey:#"offer_title"];
if ([value isKindOfClass:[NSNull class]])
cell.offerTitle.text = #"";
else
call.offerTitle.text = value;
or
id value = [voucherData objectForKey:#"offer_title"];
cell.offerTitle.text = [value isKindOfClass:[NSNull class]] ? #"" : value;
One candidate for best practice here is to use isEqual:, not isEqualToString:. That way, if what you get is not a string, you won't get an error and the equality test will be failed in good order.
On the other hand you could argue that isEqualToString: was a good choice, because when what you got was not a string, you got an error that alerted you to the issue!
EDIT: But that's wrong; see the comments below. The isEqualToString: message is coming from UIKit, not from the OP's own code.
i make some changing for noa's answer
-(NSDictionary*)safeData:(NSDictionary*)dict{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithDictionary:dict];
NSArray *keys = dictionary.allKeys;
for (int i = 0; i < keys.count; i++){
id value = [dictionary objectForKey:[keys objectAtIndex:i]];
// you can add recursive here
value = [value isKindOfClass:[NSNull class]] ? #"" : value;
[dictionary setObject:value forKey:[keys objectAtIndex:i]];
}
return dictionary; }
and use
dictionary = [self safeData:dictionary];
What that line from the console is likely telling you is that "voucherData" is not the "NSDictionary" object that you assume that it is.
Also make sure that "offerTitle" in your cell is a valid UITextField as well.

How to check if NSString returned by objectForKey is "" objective c

I'm not exactly sure how to check whether a NSString is blank or not, I've got this code...
NSString *imageName = [myItem objectForKey:#"iconName"];
if(imageName == #"")
{
}
And when I do a print on the myItem object, it comes up as..
iconName = "";
At the NSString *imageName line, I noticed in xcode in the console it says
"variable is not NSString"
Which I don't get as iconName is saved and stored on the parse.com database as a NSString.
When I run that code though it doesn't seem to realise that imageName = "";
You should use this code block when comparing strings:
if ([imageName isEqualToString:#""]){
}
You need to use isEqualToString to compare two strings. If you just use == then you are comparing two pointers.
You could also check to see if the object you are receiving is a NSString by:
if ([imageName isKindOfClass:[NSString class]])
Hope this helps.
Although you have a few answers already, here is my take.
First of all, your warning (not error) can be fixed like this:
NSString *imageName = (NSString *)[myItem objectForKey:#"iconName"];
Then, I would check to make sure that the string is not nil and that it is not blank. The easiest way to do this in objective-C is to check the length of the string, since if it nil it will return 0, and if it is empty, it will return 0:
if([imageName length] == 0)
{
// This is an empty string.
}
As #jlehr points out, if there is the possibility that imageName may not actually be stored as a string, then in order to prevent a crash you need to check first. (This may or may not be needed, depending on the logic of your application):
if ([imageName isKindOfClass:[NSString class]]
{
if([imageName length] == 0)
{
// This is an empty string.
}
}
The "variable is not NSString" is probably because objectForKey: return an id.
To should use [imageName isEqualToString:#""].

How to allow NSMutableDictionary to accept 'nil' values?

I have this statement:
[custData setObject: [rs stringForColumnIndex:2] forKey: #"email"];
where [rs stringForColumnIndex:2] obtained from a SQLite3 d/b has a value of nil. The app crashes giving me the error:
NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: email)'
Is there a way to prevent this? (like a setting for NSMutableDictionary?)
UPDATE: this is what I finally did:
[custData setObject: ([rs stringForColumnIndex:2] != nil? [rs stringForColumnIndex:2]:#"") forKey: #"email"];
There is a non-nil object called NSNull that is built specifically to represent nils in situations where "plain" nil is not acceptable. If you replace your nils with [NSNull null] object, NSDictionary will take them. You would need to check for NSNull on the way out, though.
Note that this is important only when you must differentiate between a value not being set and a value being set to nil. If your code is such that it can interpret a missing value as nil, you do not need to use NSNull at all.
It is not possible with a pure NSMutableDictionary, and in most cases you want to convert nil values into [NSNull null] or just omit them from the dictionary. Sometimes (very seldom), though, it is convenient to allow nil values, and in those cases you can use CFMutableDictionary with custom callbacks.
If you go this way, I recommend that you use CoreFoundation API for all accesses, e.g. CFDictionarySetValue and CFDictionaryGetValue.
However, if you know what you're doing, you can use toll-free bridging and cast that CFMutableDictionary to NSMutableDictionary or NSDictionary. This may be useful if you have a bunch of helpers that accept NSDictionary, and you want to use them on your modified nil-capable dictionary. (Of course, make sure that the helpers aren't surprised by nil values.)
If you do the bridging, note that:
1) NSMutableDictionary setter raises errors on nil values before bridging, so you need to use CFDictionarySetValue to set values that are potentially nil.
2) technically, we're violating a contract of NSMutableDictionary here, and things may break (e.g. in future OS updates)
3) a lot of code will be very surprised to find nil values in a dictionary; you should only pass the bridged frankendictionaries to the code that you control
See ridiculousfish's post on toll-free bridging for an explanation of why a bridged CFDictionary behaves differently from NSDictionary.
Example:
#import <Foundation/Foundation.h>
const void *NullSafeRetain(CFAllocatorRef allocator, const void *value) {
return value ? CFRetain(value) : NULL;
}
void NullSafeRelease(CFAllocatorRef allocator, const void *value) {
if (value)
CFRelease(value);
}
const CFDictionaryValueCallBacks kDictionaryValueCallBacksAllowingNULL = {
.version = 0,
.retain = NullSafeRetain,
.release = NullSafeRelease,
.copyDescription = CFCopyDescription,
.equal = CFEqual,
};
int main(int argc, const char * argv[])
{
#autoreleasepool {
CFMutableDictionaryRef cfdictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kDictionaryValueCallBacksAllowingNULL);
CFDictionarySetValue(cfdictionary, #"foo", #"bar");
CFDictionarySetValue(cfdictionary, #"boz", nil);
NSMutableDictionary *dictionary = CFBridgingRelease(cfdictionary);
NSLog(#"dictionary[foo] = %#", dictionary[#"foo"]);
NSLog(#"dictionary[foo] = %#", dictionary[[#"fo" stringByAppendingString:#"o"]]);
NSLog(#"dictionary[boz] = %#", dictionary[#"boz"]);
NSLog(#"dictionary = %#", dictionary);
NSLog(#"(dictionary isEqualTo: dictionary) = %d", [dictionary isEqualToDictionary:dictionary]);
}
return 0;
}
outputs:
dictionary[foo] = bar
dictionary[foo] = bar
dictionary[boz] = (null)
dictionary = {
boz = (null);
foo = bar;
}
(dictionary isEqualTo: dictionary) = 1
I needed to set a NSDictionary value to one that may or may not be set yet from NSUserDefaults.
What I did was wrap the values in a stringwithFormat call. Both values are not yet set so start as null. When I run without the stringwithFormat call the app crashes. So I did this and in my situation worked.
-(NSDictionary*)userDetailsDict{
NSDictionary* userDetails = #{
#"userLine":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:kSelectedLine] ],
#"userDepot":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:#"kSelected Duty Book"]]
};
return userDetails;
}

How to check if an NSDictionary or NSMutableDictionary contains a key?

I need to check if an dict has a key or not. How?
objectForKey will return nil if a key doesn't exist.
if ([[dictionary allKeys] containsObject:key]) {
// contains key
}
or
if ([dictionary objectForKey:key]) {
// contains object
}
More recent versions of Objective-C and Clang have a modern syntax for this:
if (myDictionary[myKey]) {
}
You do not have to check for equality with nil, because only non-nil Objective-C objects can be stored in dictionaries(or arrays). And all Objective-C objects are truthy values. Even #NO, #0, and [NSNull null] evaluate as true.
Edit: Swift is now a thing.
For Swift you would try something like the following
if let value = myDictionary[myKey] {
}
This syntax will only execute the if block if myKey is in the dict and if it is then the value is stored in the value variable. Note that this works for even falsey values like 0.
if ([mydict objectForKey:#"mykey"]) {
// key exists.
}
else
{
// ...
}
When using JSON dictionaries:
#define isNull(value) value == nil || [value isKindOfClass:[NSNull class]]
if( isNull( dict[#"my_key"] ) )
{
// do stuff
}
I like Fernandes' answer even though you ask for the obj twice.
This should also do (more or less the same as Martin's A).
id obj;
if ((obj=[dict objectForKey:#"blah"])) {
// use obj
} else {
// Do something else like creating the obj and add the kv pair to the dict
}
Martin's and this answer both work on iPad2 iOS 5.0.1 9A405
One very nasty gotcha which just wasted a bit of my time debugging - you may find yourself prompted by auto-complete to try using doesContain which seems to work.
Except, doesContain uses an id comparison instead of the hash comparison used by objectForKey so if you have a dictionary with string keys it will return NO to a doesContain.
NSMutableDictionary* keysByName = [[NSMutableDictionary alloc] init];
keysByName[#"fred"] = #1;
NSString* test = #"fred";
if ([keysByName objectForKey:test] != nil)
NSLog(#"\nit works for key lookups"); // OK
else
NSLog(#"\nsod it");
if (keysByName[test] != nil)
NSLog(#"\nit works for key lookups using indexed syntax"); // OK
else
NSLog(#"\nsod it");
if ([keysByName doesContain:#"fred"])
NSLog(#"\n doesContain works literally");
else
NSLog(#"\nsod it"); // this one fails because of id comparison used by doesContain
Using Swift, it would be:
if myDic[KEY] != nil {
// key exists
}
Yes. This kind of errors are very common and lead to app crash. So I use to add NSDictionary in each project as below:
//.h file code :
#interface NSDictionary (AppDictionary)
- (id)objectForKeyNotNull : (id)key;
#end
//.m file code is as below
#import "NSDictionary+WKDictionary.h"
#implementation NSDictionary (WKDictionary)
- (id)objectForKeyNotNull:(id)key {
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
#end
In code you can use as below:
NSStrting *testString = [dict objectForKeyNotNull:#"blah"];
For checking existence of key in NSDictionary:
if([dictionary objectForKey:#"Replace your key here"] != nil)
NSLog(#"Key Exists");
else
NSLog(#"Key not Exists");
Because nil cannot be stored in Foundation data structures NSNull is sometimes to represent a nil. Because NSNull is a singleton object you can check to see if NSNull is the value stored in dictionary by using direct pointer comparison:
if ((NSNull *)[user objectForKey:#"myKey"] == [NSNull null]) { }
Solution for swift 4.2
So, if you just want to answer the question whether the dictionary contains the key, ask:
let keyExists = dict[key] != nil
If you want the value and you know the dictionary contains the key, say:
let val = dict[key]!
But if, as usually happens, you don't know it contains the key - you want to fetch it and use it, but only if it exists - then use something like if let:
if let val = dict[key] {
// now val is not nil and the Optional has been unwrapped, so use it
}
I'd suggest you store the result of the lookup in a temp variable, test if the temp variable is nil and then use it. That way you don't look the same object up twice:
id obj = [dict objectForKey:#"blah"];
if (obj) {
// use obj
} else {
// Do something else
}
if ([MyDictionary objectForKey:MyKey]) {
// "Key Exist"
}
As Adirael suggested objectForKey to check key existance but When you call objectForKeyin nullable dictionary, app gets crashed so I fixed this from following way.
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
id object = dictionary;
if (dictionary && (object != [NSNull null])) {
self.name = [dictionary objectForKey:#"name"];
self.age = [dictionary objectForKey:#"age"];
}
return self;
}
if ( [dictionary[#"data"][#"action"] isKindOfClass:NSNull.class ] ){
//do something if doesn't exist
}
This is for nested dictionary structure