How to create an inline conditional assignment in Objective-C? - 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.

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.

Related

set ivars from NSDictionnary

I'm currently working on a project where the user defines some parameters in a NSDictionnary, that I'm using to setup some objects.
For example, you can ask to create a Sound object with parameters param1=xxx, param2=yyy, gain=3.5 ... Then an Enemi object with parameters speed=10, active=YES, name=zzz ...
{
active = NO;
looping = YES;
soundList = "FINAL_PSS_imoverhere_all";
speed = 100.0;
}
I then instantiate my classes, and would like to set the ivars automatically from this dictionnary.
I've actually wrote some code to check that this parameter exists, but I'm having trouble in actually setting the parameter value, especially when the parameter is non object (float or bool).
Here's what I'm doing so far :
//aKey is the name of the ivar
for (NSString *aKey in [properties allKeys]){
//create the name of the setter function from the key (parameter -> setParameter)
NSString *setterName = [aKey stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[aKey substringToIndex:1] uppercaseString]];
setterName = [NSString stringWithFormat:#"set%#:",setterName];
SEL setterSelector = NSSelectorFromString(setterName);
//Check if the parameter exists
if ([pge_object respondsToSelector:setterSelector]){
//TODO : automatically set the parameter
}
else{
[[PSMessagesChecker sharedInstance]logMessage:[NSString stringWithFormat:#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]] inColor:#"red"];
NSLog(#"Cannot find %# on %#", aKey, [dict objectForKey:#"type"]);
}
}
}
As you can see, I don't know what to do once I've found that the parameter exists on the object. I tried to use "performSelector... withObject..., but my problem is that some of the parameters are non-objects (float or bool).
I also tried to get the class of the parameter, by using the setter, but it didn't help.
Did anyone manage to do something like that?
Jack Lawrence's comment is spot on.
What you are looking for is called Key Value Coding, or just KVC.
This fundamental part of Cocoa lets you get and set any instance variable using its name as a String and a new value.
It will automatically handle coercing Objects to primitive values, so you can use it for int and float properties too.
There is also support for validating values and handling unknown properties.
see the docs
your code, without validation, could be written
for( id eachKey in props ) {
[anOb setValue:props[eachKey] forKey:eachKey];
}
or just
[anOb setValuesForKeysWithDictionary:props];
as Jack said.
For the non-object parameters you have to put them into an object, for example NSNumber or NSValue. You can then add these objects into your dictionary.
For Example:
float f = 0.5;
NSNumber f_obj = [NSNumber numberWithFloat:f];

How to initialize a string object with null value?

Is it possible to initialize NSString object with null value?
Something like:
NSString *strNullTest = [[NSString alloc]initWithString:[NSNull null]];
When I am doing this it's giving warning:
instance method -"null" not found return type default to id.
Does anyone knows the right way to initialize a string object with null?
there is no "null string" exist in objc. why you want a null string?
either you can use an empty string #"" or just use nil
if you want to put the string in a array or dictionary, [NSNull null] is good. all you need to do is to compare the value with [NSNull null] fist by object == [NSNull null]
You cannot initialize an NSString with using the NSNull instance.
It's not clear what you want, exactly. Perhaps you want a nil pointer:
NSString *str = nil;
nil is the same as NULL, but we usually use it where an object pointer is required.
Or maybe you want an empty (zero-length) string:
NSString *str = #"";
This string is a real object (str != nil) but contains no characters.
How about this:
NSObject *obj1 = #"string";
NSObject *obj2 = [NSNull null];
Then to check for null you use isKindOfClass: method.

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;
}

NSDictionaray dictionaryWithObjectsAndKeys adding NULL value

I want to create a NSDictionary with +[NSDictionary dictionaryWithObjectsAndKeys:]. One of my keys has a string but the string can sometimes be nil. If the string is nil, any other value key pairs I put afterward will be ignored because the list is prematurely terminated. What is the standard way to deal with the possibility that there might be a value with nil in a NSDictionary?
You need to check if the string is null. If it is, add [NSNull null] instead of your string.
Creating NSDictionary objects can be combersome if you have many objects, that if they are nil, should not be included in the dictionary.
To me NSNull is as big a problem as it is the solution. When creating NSDictionary objects for usage in other objects, you can go two ways when dealing with nil values. Either you add NSNull objects to your dictionary - and then check for NSNull values when reading the values. This makes pretty code at the point of creation, but it becomes messy when reading out the values. Ex. should you check all keys in the dictionary, or are some garanteed to be not nil? And if an NSNull value is not filtered out it is bound to make exceptions when trying to send the object messages.
The second way is to just not add NSNull values to the dictionary. This is convinient when reading the NSDictionary, as the [someDictionary objectForKey:someKey] simply returns nil when the key is not set. This approach makes it pretty this easy when reading the values, but it really is bound to be messy code on creation. Ex. Creating a NSMutableDictionary, checking for nil before adding values, and finally returning an immutable copy?
Solution
To me the solution has been to create two categories, which in essense makes it easy for both the creating and the reading ends.
When creating the NSDictionary, you simply wrap you possible-nil values like this
[NSDictionary dictionaryWithObjectsAndKeysIngoringNull:
[NSNull nullWhenNil:val1], #"value1",
[NSNull nullWhenNil:val2], #"value2",
...
nil];
This code is almost as simple as approach number one, but it makes it substantially easier when reading out the values.
The categories is as follows (works in both ARC enabled and non-ARC code):
NSDictionary addition:
#implementation NSDictionary (NullAddition)
+ (id)dictionaryWithObjectsAndKeysIngnoringNull:(id)firstObject, ... {
NSMutableArray* objects = [NSMutableArray array];
NSMutableArray* keys = [NSMutableArray array];
va_list args;
va_start(args, firstObject);
for (id object = firstObject; object; object = va_arg(args, id)) {
id key = va_arg(args, id);
if (!key)
break;
if (![object isKindOfClass:[NSNull class]]) {
[objects addObject:object];
[keys addObject:key];
}
}
va_end(args);
return [self dictionaryWithObjects:objects forKeys:keys];
}
#end
NSNull addition
#implementation NSNull (NullAddition)
+ (id)nullWhenNil:(id)obj {
return (obj ? obj : [self null]);
}
#end
Good luck!
Attempting to insert data (key or value) into an NSDictionary will result in a Run Time Error. Therefore, you do not have to deal with null data in the dictionary.
'-[__NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: Test)'
and
'-[__NSCFDictionary setObject:forKey:]: attempt to insert nil key'
However, this means you are responsible for checking the validity of the data before putting it into the dictionary to prevent crashes.
I encounter a similar problem and google take me here. I need to add some kv into the dictionary whose value may be nil. Inspired by Trenskow's answer, I modify that function to support insert nil value, not terminate as before.
#define KV_END NSNull.null
NSDictionary *NSDictionaryOfKV(id firstKey, ...) {
__auto_type dict = [NSMutableDictionary dictionary];
va_list args;
va_start(args, firstKey);
for (id key = firstKey; key != NSNull.null; key = va_arg(args, id)) {
id obj = va_arg(args, id);
if (obj == NSNull.null) {
break;
}
if (key) {
dict[key] = obj;
}
}
va_end(args);
return dict.copy;
}
__auto_type value2 = nil;
__auto_type dict = NSDictionaryOfKV(key1, value1, key2, value2, key3, value3, KV_END);
You will got
{
key1: value1,
key3: value3
}
You can change the KV_END to anything you want.
This make sense when your value is a variable, you can use it as-is, don't need to wrap it or like value ?: NSNull.null.

Why does fast enumeration not skip the NSNumbers when I specify NSStrings?

I thought that I knew how to use fast enumeration, but there is something I don't understand about it. If I create three NSString objects and three NSNumber objects and put them in an NSMutableArray:
NSString *str1 = #"str1";
NSString *str2 = #"str2";
NSString *str3 = #"str3";
NSNumber *nb1 = [NSNumber numberWithInt:1];
NSNumber *nb2 = [NSNumber numberWithInt:2];
NSNumber *nb3 = [NSNumber numberWithInt:3];
NSArray *array = [[NSArray alloc] initWithObjects:str1, str2, str3, nb1, nb2, nb3, nil];
then I make do fast enumeration on all NSString objects, like this:
for (NSString *str in array) {
NSLog(#"str : %#", str);
}
In the console, I get this result :
2011-08-02 13:53:12.873 FastEnumeration[14172:b603] str : str1
2011-08-02 13:53:12.874 FastEnumeration[14172:b603] str : str2
2011-08-02 13:53:12.875 FastEnumeration[14172:b603] str : str3
2011-08-02 13:53:12.875 FastEnumeration[14172:b603] str : 1
2011-08-02 13:53:12.876 FastEnumeration[14172:b603] str : 2
2011-08-02 13:53:12.876 FastEnumeration[14172:b603] str : 3
I logged only the NSStrings, but I get a line for every object in the array, even the NSNumbers and I don't understand why. Does fast enumeration always use every object contained in an array?
When you write a forin loop like that, it casts every object in the array as an NSString, then prints them out as requested.
If you want only the NSStrings, you would need to write something like this:
for (id obj in array) {
if ([obj isKindOfClass:[NSString class]]) {
NSLog(#"str: %#", obj);
}
}
The for all loop doesn't know the difference between NSStrings and Integers -- it will simply go through the entire array, cast each as an NSString, and print them out as you asked.
I'm pretty sure that fast enumeration returns all objects in the array- all that you're doing in for (NSString *str in array) is typecasting str to an NSString. In the body of the loop you need to check the class of the returned object to make sure that it is an NSString.
for(NSString *str in array)
{
if([str isKindOfClass:[NSString class]])
NSLog(#"str : %#", str);
}
Objective-C is dynamically typed, meaning that at runtime (when the loop actually runs), objects are all effectively one type (id) with different classes. The language allows optional compile-time static typing, but all that does is check whether the messages you're sending are valid for the type you've marked. It doesn't actually change the behavior of your program. If you cast an object to be a different type than it actually is, all you're doing is lying to the compiler and defeating its type-checker.
Every object that descends from NSObject implements the method - (NSString)description, %# in Objective-C formate string will take the corresponding argument for the %# and call its description method, Most subclasses of NSObject will implement there own version of - (NSString)description. The same thing happens when you type
> po anObject
in the debugger.
for (NSString *str in array) {
is a way to enumerate through all the elements in array.
You expectative that by specifying NSString you get only the objects of that type is not correct. Rather, all the objects pointers are cast to that type (NSString*).
Have a look at Fast Enumeration in The Objective-C Programming Language guide.
I don't understand where is the unexpected behavior, using the enhanced for loop in an NSMutableArray will just iterate thru every single object in the array which in your case is 6, the result is correct and expected.
The numbers will just get casted to Strings.
in fast enumeration no typecasting,just assigning the pointer into new object