NSDictionary and EXC_BAD_ACCESS - objective-c

Tried to find the answer here and eventually found a clue on another site. Posting here in case anyone searches here and has the same problem.
NSDictionary *d = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:#"foo", YES, 42, nil]
forKeys:[NSArray arrayWithObjects:#"bar", #"baz", #"count", nil]];
This produces:
Program received signal: "EXC_BAD_ACCESS"
What is the cause of this?

YES and 42 are not object pointers. You're trying to create an NSArray, which can only contain objects, and you're passing in values that are not pointers to objects. You'll crash for the same reason that
[YES description];
will crash -- YES is not a valid object pointer.

For one thing, in your array, YES and 42 are not objects. Try using [NSNumber numberWithInt:42] there. You should have got a compiler warning there.

A int nor a BOOL are objects and therefore cannot be part of a NSDictionary. Instead of using int's and bools use their object equivalent of a NSNumber. You can easily store a int using:
[NSNumber numberWithInt:(int)]
You can store a BOOL with:
[NSNumber numberWithBool:(BOOL)]

Related

Retrieving NSArray From an NSDictionary

I am trying to get an array full of my data, I keep getting an BAD_ACCESS error when I run this though at the calling the array which I have not included here but I even commented that code out and tried just calling it to the log and still get the BAD_ACCESS error. The array is stored in a dictionary that contains a one key that is a number. I am not sure what I am doing wrong here.
ISData *is = [[ISData alloc] init];
NSDictionary *dic = [is getData:#"right" : isNumber];
NSArray *array = [[NSArray alloc] initWithArray:[dic valueForKey:#"2"]];
NSString *out = [array objectAtIndex:0];
How the dictionary is created:
NSNumber* key = [NSNumber numberWithInt:isNumber];
NSArray *values = [NSArray arrayWithObjects:[NSString stringWithUTF8String:name], [NSString stringWithUTF8String:desc], [NSString stringWithUTF8String:intent], nil];
[dic setObject:values forKey:key];
You don't say exactly where it crashes, and you don't have an obvious crashing bug here, so it's hard to diagnose your actual issue. This could be a memory management thing that's outside the code you've presented. But a couple things are going on here that are suspicious:
You should never have a bare [MyClass alloc] without the -init call. Your init should call super's init, which is responsible for setting up the new object.
Your -valueForKey: should be -objectForKey:. The difference is probably unimportant in this case, but the former is used for "KVC" coding, which you're not using. If you set it as object, get it as object.
Your #"2" as the key into the dictionary doesn't match your input, which is an NSNumber. NSNumbers are not string versions of numbers, so you're unlikely to find any value there. Instead, use the same [NSNumber numberWithInt:2] pattern.
It is most likely that your array is empty. You can try print NSLog(#"count = %d", array.count); to see if that's the case.
If your dic is set up in the second block of code, then what's that NSDictionary *dic = [is getData:...] thing in the first block?
And is there a reason you cannot set up your array directly? Is there a reason for you to use a dictionary when it has only one key?

Can NSDictionary contain different types of objects as values?

Why does the following result in a BAD_ACCESS error?
NSDictionary *header=[[NSDictionary alloc]initWithObjectsAndKeys:#"fred",#"title",1,#"count", nil];
Can you have different types of objects as values in NSDictionary, including another NSDictionary?
You can put any type of object into an NSDictionary. So while #"fred" is OK, 1 is not, as an integer is not an object. If you want to put a number in a dictionary, wrap it in an NSNumber:
NSDictionary *header = { #"title": #"fred", #"count": #1 };
Not the way you have it. The number 1 is a primitive and the NSArray object can hold only objects. Create a NSNumber for the "1" and then it will store it.
An NSDictionary can only contain Objective-C objects in it (such as NSString and NSArray), it cannot contain primitive types like int, float, or char*. Given those constraints, heterogeneous dictionaries are perfectly legal.
If you want to include a number such as 1 as a key or value, you should wrap it with an NSNumber:
NSDictionary *header=[[NSDictionary alloc] initWithObjectsAndKeys:
#"fred", #"title",
[NSNumber numberWithInt:1], #"count",
nil];
The only requirement is that is be an object. It's up to you to handle the objects properly in your code, but presumably, you can keep track of their types based on the keys.
1 is not an object. If you want t o put a number into a dictionary you may want to convert it to an NSNumber.

How does NSDictionary handle NIL objects?

Consider the code below. In essence, we get 2 strings, then we add these values to the NSDictionary.
However, i hit a weird bug. When fbAccessTokenKey is 0x0 (or nil), then twitterToken would not be added as well.
NSString *fbAccessTokenKey=[[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken=[[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSDictionary *params= [[NSDictionary alloc] initWithObjectsAndKeys:
fbAccessTokenKey, #"fb_access_token",
twitterToken, #"twitter_access_token",
nil
];
Why is this happening, and what is a good way of resolving this?
nil is used as a 'sentinel' for marking the "end of arguments" list. If twitterToken was nil, the runtime would go through your arguments, and once it got to twitterToken, it would think that it was up to the end of your list of objects and keys. This is due to the way that C/Obj-C is implemented when it comes to list arguments.
The alternative safe way to do it is to use an NSMutableDictionary, and check to see if your values are non-nil, then add them to the mutable dictionary like this:
NSString *fbAccessTokenKey = [[UserStockInfo sharedUserStockInfo] getFBAccessTokenKey];
NSString *twitterToken = [[UserStockInfo sharedUserStockInfo] getTwitterAccessTokenKey];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (fbAccessTokenKey) [params setObject:fbAccessTokenKey forKey:#"fb_access_token"];
if (twitterToken) [params setObject:twitterToken forKey:#"twitter_access_token"];
For more technical info, there's a good article on Cocoa with Love: http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
You can use the NSNull object.
Documentation here:
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNull_Class/Reference/Reference.html
Rather than initializing with initWithObjectAndKeys. Why not instantiate an NSMutableDictionary and then add the key value pairs (or not if the key is null)?
NSMutableDictionary * params = [[NSMutableDictionary alloc] init];
if (fbAccessTokenKey)
[params setObject:fbAccessTokenKey forKey:#"fb_access_token];
// ... etc
You could cast it back to an NSDictionary later if you want to keep it immutable from that point.
Update
Just a note in response to Josh's comment, I should clarify that of course the cast will not magically convert the params NSMutableDictionary to an NSDictionary. But if you are passing it to code which requires an NSDictionary, the cast will let you treat it as such.
Josh's comment includes this code:
NSMutableDictionary * md = [[NSMutableDictionary alloc] init];
NSDictionary * d = (NSDictionary *)md;
[d setObject:#"Yes I am" forKey:#"Still mutable?"];
NSLog(#"%#", d); // Prints { "Still mutable?" = Yes I am; }
This will generate the following compiler warning (and for me, with warnings generating errors, a compile error):
file:blah.m: error: Semantic Issue: 'NSDictionary' may not respond to 'setObject:forKey:'

Objective C memory management question with NSArray

I am loading an array with floats like this:
NSArray *arr= [NSArray arrayWithObjects:
[NSNumber numberWithFloat:1.9],
[NSNumber numberWithFloat:1.7],
[NSNumber numberWithFloat:1.6],
[NSNumber numberWithFloat:1.9],nil];
Now I know this is the correct way of doing it, however I am confused by the retail counts.
Each Object is created by the [NSNumber numberWithFloat:] method. This gives the object a retain count of 1 dosnt it? - otherwise the object would be reclaimed
The arrayWithObjects: method sends a retain message to each object.
This means each object has a retain cont of 2. When the array is de-allocated each object is released leaving them with a retain count of 1.
What have I missed?
The NSNumber numberWithFloat: method isn't returning a retained object.
In general unless you're using alloc, copy or new you can presume that you're getting a object that has a retain count of zero. As such the only retain that's taking place is when the NSArray has the objects added to it.
There's a good blog about such things over at: http://interfacelab.com/objective-c-memory-management-for-lazy-people/
There is no need to release those objects.
The arrayWithObjects: and numberWithFloat: creates object you do not own.

Objective-C dictionary inserting a BOOL

OK, I'm a little confused.
It's probably just a triviality.
I've got a function which looks something like this:
- (void)getNumbersForNews:(BOOL)news andMails:(BOOL)mails {
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setValue:news forKey:#"getNews"];
[parameters setValue:mails forKey:#"getMails"];...}
It doesn't matter whether I use setValue:forKey: or setObject:ForKey:, I'm always getting a warning:
"Passing argument 1 of set... makes pointer from integer without a cast"...
How on earth do I insert a bool into a dictionary?
Values in an NSDictionary must be objects. To solve this problem, wrap the booleans in NSNumber objects:
[parameters setValue:[NSNumber numberWithBool:news] forKey:#"news"];
[parameters setValue:[NSNumber numberWithBool:mails] forKey:#"mails"];
Objective-C containers can store only Objective-C objects so you need to wrap you BOOL in some object. You can create a NSNumber object with [NSNumber numberWithBool] and store the result.
Later you can get your boolean value back using NSNumber's -boolValue.
Modern code for reference:
parameters[#"getNews"] = #(news);
A BOOL is not an object - it's a synonym for an int and has 0 or 1 as its values. As a result, it's not going to be put in an object-containing structure.
You can use NSNumber to create an object wrapper for any of the integer types; there's a constructor [NSNumber numberWithBool:] that you can invoke to get an object, and then use that. Similarly, you can use that to get the object back again: [obj boolValue].
You can insert #"YES" or #"NO" string objects and Cocoa will cast it to bool once you read them back.
Otherwise I'd suggest creating dictionary using factory method like dictionaryWithObjectsAndKeys:.
Seeing #Steve Harrison's answer I do have one comment. For some reason this doesn't work with passing object properties like for e.g.
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews] forKey:#"news"];
This sets the news key to null in the parameter NSDictionary (for some reason can't really understand why)
My only solution was to use #Eimantas's way as follows:
[parameters setValue:[NSNumber numberWithBool:myObject.hasNews ? #"YES" : #"NO"] forKey:#"news"];
This worked flawlessly. Don't ask me why passing the BOOL directly doesn't work but at least I found a solution. Any ideas?