Reputation: 21
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.
Upvotes: 1
Views: 531
Reputation: 41
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:
NSNull
(not the best solution, and storing two different types of objects under the same key in a dictionary is not the best practice)NSNull
some default values in the case if there's no myPet. Like setting @NO
for isHungry and @0
for agemyMap[@"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.
Upvotes: 1
Reputation: 3368
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.
Upvotes: 0
Reputation: 52538
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.
Upvotes: 1