So I've tested this, but just wanting to make sure it wasn't some random undefined behavior. I want to use the shorthand ternary on a dictionary but I want it to return a default value when it is not set. Here is the test I wrote:
NSMutableDictionary *test = [NSMutableDictionary new];
test[#"test_1"] = #NO;
test[#"test_2"] = #YES;
BOOL test1 = [test[#"test_1"] boolValue] ?: NO;
BOOL test2 = [test[#"test_2"] boolValue] ?: NO;
BOOL test3 = [test[#"test_3"] boolValue] ?: NO;
LogDebug(#"test1 = %#", (test1 ? #"YES" : #"NO"));
LogDebug(#"test2 = %#", (test2 ? #"YES" : #"NO"));
LogDebug(#"test3 = %#", (test3 ? #"YES" : #"NO"));
Now I got the correct value for test3, but I'm wondering whether thats just a random fluke of undefined behavior. I'm wondering this because when I checked for that value in the debugger by typing this in the console:
po [test[#"test_3"] boolValue]
I got:
error: no known method '-boolValue'; cast the message send to the method's return type
error: 1 errors parsing expression
Are the results of test3 reliable?
NSDictionary (and its subclass, NSMutableDictionary) is documented as returning nil for a key with no value.
In Objective-C, sending a message to nil is documented to return NO when you expect a BOOL result.
Therefore, if you want the default value to be NO, this suffices:
BOOL test3 = [test[#"test_3"] boolValue];
If you only intend to store NSString keys and NSNumber values (which #YES and #NO are) in the dictionary, you can declare it like this:
NSMutableDictionary<NSString *, NSNumber *> *test = [NSMutableDictionary new];
And then you can use a property accessor if you prefer:
BOOL test3 = test[#"test_3"].boolValue;
If you want a different default value (and the only other possibility for BOOL values is YES), then #trojanfoe's suggestion of creating a method is what I'd recommend, but I'd probably make a more general category on NSDictionary like this:
// NSDictionary+kailoon.h
#interface NSDictionary<KeyType, ObjectType> (kailoon)
- (ObjectType)kailoon_objectForKey:(KeyType)key default:(ObjectType)defaultObject;
#end
// NSDictionary+kailoon.m
#implementation NSDictionary (kailoon)
- (id)kailoon_objectForKey:(id)key default:(id)defaultObject {
id value = self[key];
return value ? value : defaultObject;
}
#end
Then I'd use it like this:
BOOL test3 = [test kailoon_objectForKey:#"test_3" default:#YES].boolValue;
As for your debugger problem, it's incorrect to use po to print a BOOL, because po means “print object” and a BOOL is not an object. (It is a primitive.) So you should just use p. But that's not really the problem here. To fix the “no known method” problem, you use a cast, like this:
(lldb) p (BOOL)[test[#"test_3"] boolValue]
(BOOL) $1 = NO
The output you are getting from your code is correct and defined.
First off, your code can be simplified to:
BOOL test1 = [test[#"test_1"] boolValue];
BOOL test2 = [test[#"test_2"] boolValue];
BOOL test3 = [test[#"test_3"] boolValue];
You will get NO for any key that doesn't exist. If test[#"some_nonexistent_key"] returns nil, calling boolValue on nil results in NO.
The debugger is just getting confused because it doesn't know what to do with the call to boolValue on nil since there are more than one possible boolValue methods (NSNumber and NSString).
That code is reliable, but only for booleans and only when the default value is NO, as non-existence of the value in the dictionary will give NO and hence the default value will be used.
However it's somewhat clumsy as:
[test[#"test_3"] boolValue] ?: NO;
is basing the result of the ternary expression on whether the boolean stored in the NSNumber is YES or NO, when what you want to know is is the value set in the dictionary?.
This is better:
BOOL test1 = test[#"test_1"] != nil ? [test[#"test_1"] boolValue] : NO;
and will use the default value only if the dictionary does not contain the specified key. This pattern can also be extended to other types and can also be used if the default value is YES.
Wrapped into a method (recommended), it would be the following (this code also checks the value type):
- (BOOL)boolForKey:(NSString *)key defaultValue:(BOOL)defaultValue
{
id value = self.dictionary[key];
return [value isKindOfClass:[NSNumber class]] ? [value boolValue] : defaultValue;
}
Related
My Pet class has 2 properties: BOOL isHungry and NSNumber *age.
I want to put the properties of Pet myPet into NSMutableDictionary *myMap.
This is my code is Java. I am trying to write an equivalent in Objective-C
myMap.put("isHungry", myPet == null ? null : myPet.isHungry);
myMap.put("age", myPet == null ? null : myPet.age);
This is my current Objective-C version:
[myMap addEntriesFromDictionary:#{
#"isHungry" : myPet ? myPet.isHungry : (NSInteger)[NSNull null],
#"age" : myPet ? myPet.age : [NSNull null],
}];
The error for the second line is the following:
Incompatible operand types ('int' and 'NSNull * _Nonnull')
The compiler stopped complaining about the first line when I added (NSInteger).
If I put the same on the second line, the error goes away, but the compiler complains about the first line again:
Collection element of type 'long' is not an Objective-C object
I am a noob in Obj-C and I am totally lost. I would also like to know the best practice for Obj-C.
Dictionaries in Objective C can only store objects, and only existing objects.
You turn a boolean or arithmetic value like myPet.isHungry into an NSNumber object by writing #(myPet.isHungry). You create an object that can stand in for nil by writing [NSNull null].
When you try to extract a value from a dictionary, you get an object or nil. You check if the object represents nil by checking
if (value == nil || value == [NSNull null])
The second comparison works because there is always ever only one NSNull object.
If you know that the value is an NSNumber object, you can use boolValue or integerValue etc. to extract the value.
Your isHungry is a BOOL. Arrays and dictionaries can store only objects. But BOOL and NSInteger are primitive types and not objects (that's why you get the error). But you can convert it to an object (NSNumber in this case) and add it to a dictionary.
You can convert BOOL value to NSNumber in two ways, by adding # in front of a value or by using numberWithBool:
Example:
NSNumber *isHungry = #(myPet.isHungry); // OR
NSNumber *isHungry = [NSNumber numberWithBool:myPet.isHungry];
You can do it inline so your code will look (and work) like:
[myMap addEntriesFromDictionary:#{
#"isHungry" : myPet ? #(myPet.isHungry) : [NSNull null],
#"age" : myPet ? myPet.age : [NSNull null],
}];
When you retrieve data from the dictionary you'll get an NSNumber you stored before. But you can convert it back to a BOOL if needed.
// getting BOOL back
NSNumber *isHungryObj = myMap[#"isHungry"]; // it must be NSNumber not NSNull!
BOOL isHungry = isHungry.boolValue;
But in the case above you have to be sure that your stored object is actually a NSNumber and not NSNull. Because in the case of NSNull the app will crash because NSNull is not NSNumber and doesn't respond to boolValue.
So to avoid that you'll either:
always have to check the returned object against NSNull (not the best solution, and storing two different types of objects under the same key in a dictionary is not the best practice)
depending on your needs it may be wiser to store instead of NSNull some default values in the case if there's no myPet. Like setting #NO for isHungry and #0 for age
or you can check the existence of myPet before adding values and if it doesn't exist then just don't add anything to myMap. In this case if you don't add anything to myMap, then calling myMap[#"isHungry"] will return nil.
It is another variant of null in Objective-C. It's easier to check for nil than NSNull and nothing bad will happen even if you send some message to nil. In Objective-C sending messages to nil is allowed. You can't store nil in a dictionary as you can do with NSNull, but you can compare objects to nil.
Sample code for the 3rd option:
// adding to a dictionary, does the same thing as your code
if (myPet != nil) // OR if (myPet)
{
myMap[#"isHungry"] = #(myPet.isHungry);
myMap[#"age"] = myPet.age;
}
// retrieving
if (myMap[#"age"])
{
// number exists, you can do something with it
}
And since nil can have messages sent to it without a problem, sometimes you don't even need to check for nil, for example in such case:
if ([myMap[#"age"] integerValue] == 5) // returns YES if it's 5 and NO in any other case even if #"age" wasn't set and is nil
Hope this helps.
As you have a class Pet with #property BOOL isHungry; and #property NSNumber *age; and your myMap is NSMutableDictionary your solution should look like..
Pet *myPet = [[Pet alloc] init];
myPet.age = #(2);
myPet.isHungry = YES;
NSMutableDictionary *myMap = [[NSMutableDictionary alloc] init];
if (myPet!=nil) {
[myMap addEntriesFromDictionary:#{
#"isHungry" : #(myPet.isHungry),
#"age" : myPet.age
}];
}
// with this you store only the values of Pet
NSLog(#"%#",myMap.description);
// but that goes even easier..
NSMutableDictionary *myDict = [NSMutableDictionary new];
myDict[#"Pet1"] = myPet;
NSLog(#"%#",myDict.description);
Pet *petInDict = myDict[#"Pet"];
NSLog(#"age=%# isHungry=%#",petInDict.age, (petInDict.isHungry ? #"YES":#"NO") );
// should be age=(null) isHungry=NO
// because we stored with key myDict[#"Pet1"] and not myDict[#"Pet"]
// ok lets take the key we used
Pet *pet1 = myDict[#"Pet1"];
NSLog(#"age=%# isHungry=%#",pet1.age, (pet1.isHungry ? #"YES":#"NO") );
As there are generic data types that are not subclasses of NSObject you cant store them in dictionarys without making them to objects.
#(yournumber) // converts to NSNumber
#(YES) // converts to NSNumber = 1
#(NO) // converts to NSNumber = 0
#[#(1),#(2),#(3)] // converts to an NSArray with 3 NSNumbers
#{} // this one you know allready, its a NSDictionary
#"hello" // well NSString of course
#selector(name:) // thats a pointer to a method with name:, of type SEL
...
#{#"key1":#YES, #"key2":#NO}
// it is possible to convert BOOL directly
you can also initiate this way, but you see it can become looking strange
NSMutableDictionary *syntaxsugar = [(#{#"isHungry":#(myPet.isHungry), #"age":myPet.age}) mutableCopy];
mutableCopy generates a mutable copy of the leading Datatype which is NSDictionary.
How should I go about getting a bool value that I can assign true, false and nil to in Objective-C? What is the Objective-C way of doing this? Much like C#'s Nullable.
I'm looking to be able to use the nil value to represent undefined.
An NSNumber instance might help. For example:
NSNumber *yesNoOrNil;
yesNoOrNil = [NSNumber numberWithBool:YES]; // set to YES
yesNoOrNil = [NSNumber numberWithBool:NO]; // set to NO
yesNoOrNil = nil; // not set to YES or NO
In order to determine its value:
if (yesNoOrNil == nil)
{
NSLog (#"Value is missing!");
}
else if ([yesNoOrNil boolValue] == YES)
{
NSLog (#"Value is YES");
}
else if ([yesNoOrNil boolValue] == NO)
{
NSLog (#"Value is NO");
}
On all Mac OS X platforms after 10.3 and all iPhone OS platforms, the -[NSNumber boolValue] method is guaranteed to return YES or NO.
I think you will need to use some class for that, e.g. wrap bool to NSNumber object.
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
I want to get the type of NSNumber instance.
I found out on http://www.cocoadev.com/index.pl?NSNumber this:
NSNumber *myNum = [[NSNumber alloc] initWithBool:TRUE];
if ([[myNum className] isEqualToString:#"NSCFNumber"]) {
// process NSNumber as integer
} else if ([[myNum className] isEqualToString:#"NSCFBoolean"]) {
// process NSNumber as boolean
}
Ok, but this doesn't work, the [myNum className] isn't recognized by the compiler.
I'm compiling for iPhone.
I recommend using the -[NSNumber objCType] method.
It allows you to do:
NSNumber * n = [NSNumber numberWithBool:YES];
if (strcmp([n objCType], #encode(BOOL)) == 0) {
NSLog(#"this is a bool");
} else if (strcmp([n objCType], #encode(int)) == 0) {
NSLog(#"this is an int");
}
For more information on type encodings, check out the Objective-C Runtime Reference.
You can get the type this way, no string comparisons needed:
CFNumberType numberType = CFNumberGetType((CFNumberRef)someNSNumber);
numberType will then be one of:
enum CFNumberType {
kCFNumberSInt8Type = 1,
kCFNumberSInt16Type = 2,
kCFNumberSInt32Type = 3,
kCFNumberSInt64Type = 4,
kCFNumberFloat32Type = 5,
kCFNumberFloat64Type = 6,
kCFNumberCharType = 7,
kCFNumberShortType = 8,
kCFNumberIntType = 9,
kCFNumberLongType = 10,
kCFNumberLongLongType = 11,
kCFNumberFloatType = 12,
kCFNumberDoubleType = 13,
kCFNumberCFIndexType = 14,
kCFNumberNSIntegerType = 15,
kCFNumberCGFloatType = 16,
kCFNumberMaxType = 16
};
typedef enum CFNumberType CFNumberType;
If all you want is to differentiate between booleans and anything else, you can make use of the fact that boolean NSNumbers always return a shared instance:
NSNumber *num = ...;
if (num == (void*)kCFBooleanFalse || num == (void*)kCFBooleanTrue) {
// num is boolean
} else {
// num is not boolean
}
NSNumber explicitly doesn't guarantee that the returned type will match the method used to create it, so doing this at all is probably a bad idea.
However, you could probably do something like this (you could also compare to objc_getClass("NSCFNumber") etc., but this is arguably more portable):
Class boolClass = [[NSNumber numberWithBool:YES] class];
/* ... */
if([myNum isKindOfClass:boolClass]) {
/* ... */
}
In Swift:
let numberType = CFNumberGetType(answer)
switch numberType {
case .charType:
//Bool
case .sInt8Type, .sInt16Type, .sInt32Type, .sInt64Type, .shortType, .intType, .longType, .longLongType, .cfIndexType, .nsIntegerType:
//Int
case .float32Type, .float64Type, .floatType, .doubleType, .cgFloatType:
//Double
}
Use the method -[NSNumber objCType] method to get the type.
If the type's equal to #encode(BOOL), or the number itself is kCFBooleanFalse, or kCFBooleanTrue, it's a boolean.
If it's anything else but 'c', it's a number.
If it's 'c', what appears to be the only way supported way, without checking against private class names, or comparing against undocumented singletons, is to turn make an array of one element, the number, and then use NSJSONSerialization to get the string representation. Finally, check if the string representation contains the string "true" or "false". Here is the full code for checking if an NSNumber is a BOOL:
-(BOOL)isBool
{
if(!strcmp(self.objCType, #encode(BOOL)) ||
self == (void*)kCFBooleanFalse ||
self == (void*)kCFBooleanTrue)
{
return YES;
}
if(strcmp(self.objCType, "c"))
{
return NO;
}
NSString * asString = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:#[self] options:kNilOptions error:nil] encoding:NSUTF8StringEncoding];
return [asString containsString:#"true"] || [asString containsString:#"false"];
}
Note that using NSJSONSerialization is slow and if #NO/#YES ever stops always equalling kCFBooleanFalse/kCFBooleanTrue, then this method probably shouldn't be used in a tight loop.
The reason the compiler warns you and it doesn't work is because -[NSObject className] is declared in a category on NSObject on Mac OS X (in NSScriptClassDescription.h) and not declared on iPhone. (It doesn't support AppleScript, obviously.) NSStringFromClass([myNum class]) is what you should use to be safe across all platforms. Odds are that -className is declared as a simple wrapper around NSStringFromClass() anyway...
NSString *classString = NSStringFromClass([myNum class]);
That should ger the string you want.
To check that NSNumber contains a bool value Try this:
if (strcmp([myNumber objCType], [#(YES) objCType]) == 0)
NSLog(#"%#", [myNumber boolValue] ? #"true" : #"false");
objCType documentation states that The returned type does not necessarily match the method the number object was created with
Secondly, other methods of comparing the class of number to a given class type or assuming boolean number instances to be shared singletons are not documented behaviour.
A more(not completely though) reliable way is to depend on NSJSONSerialisation as it correctly recognises number instances created with bool and outputs true/false in json. This is something we can expect Apple to take care of while moving with new SDKs and on different architectures. Below is the code:
+(BOOL) isBoolType:(NSNumber*) number {
NSError* err;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:#{#"key":number}
options:0
error:&err];
NSString* jsonString = [[NSString alloc]
initWithData:jsonData
encoding:NSUTF8StringEncoding];
return [jsonString containsString:#"true"]
|| [jsonString containsString:#"false"];
}
Swift Version
NSNumber is a class-cluster so each underlying type can be figured from the instance. This code avoids hard-coding the different NSNumber types by creating an instance of the expected type, and then comparing it against the unknown type.
extension NSNumber {
var isBool: Bool {
return type(of: self) == type(of: NSNumber(booleanLiteral: true))
}
}
check object is of NSNumber type :
if([obj isKindOfClass:NSClassFromString(#"__NSCFNumber")])
{
//NSNumber
}
Well, for integers I would use NSNumber. But YES and NO aren't objects, I guess. A.f.a.i.k. I can only add objects to an NSDictionary, right?
I couldn't find any wrapper class for booleans. Is there any?
You use NSNumber.
It has init... and number... methods that take booleans, just as it does integers and so on.
From the NSNumber class reference:
// Creates and returns an NSNumber object containing a
// given value, treating it as a BOOL.
+ (NSNumber *)numberWithBool:(BOOL)value
and:
// Returns an NSNumber object initialized to contain a
// given value, treated as a BOOL.
- (id)initWithBool:(BOOL)value
and:
// Returns the receiver’s value as a BOOL.
- (BOOL)boolValue
The new syntax since Apple LLVM Compiler 4.0
dictionary[#"key1"] = #(boolValue);
dictionary[#"key2"] = #YES;
The syntax converts BOOL to NSNumber, which is acceptable to NSDictionary.
If you are declaring it as a literal and you are using clang v3.1 or above, you should use #NO / #YES if you are declaring it as a literal. E.g.
NSMutableDictionary* foo = [#{ #"key": #NO } mutableCopy];
foo[#"bar"] = #YES;
For more info on that:
http://clang.llvm.org/docs/ObjectiveCLiterals.html
As jcampbell1 pointed out, now you can use literal syntax for NSNumbers:
NSDictionary *data = #{
// when you always pass same value
#"someKey" : #YES
// if you want to pass some boolean variable
#"anotherKey" : #(someVariable)
};
Try this:
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setObject:[NSNumber numberWithBool:TRUE] forKey:#"Pratik"];
[dic setObject:[NSNumber numberWithBool:FALSE] forKey:#"Sachin"];
if ([dic[#"Pratik"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Pratik'");
}
else
{
NSLog(#"Boolean is FALSE for 'Pratik'");
}
if ([dic[#"Sachin"] boolValue])
{
NSLog(#"Boolean is TRUE for 'Sachin'");
}
else
{
NSLog(#"Boolean is FALSE for 'Sachin'");
}
The output will be as following:
Boolean is TRUE for 'Pratik'
Boolean is FALSE for 'Sachin'