PolinaGo
PolinaGo

Reputation: 21

How to create an inline conditional assignment in Objective-C?

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

Answers (3)

testdrive
testdrive

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:

  • 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.

Upvotes: 1

Ol Sen
Ol Sen

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

gnasher729
gnasher729

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

Related Questions