Can't create NSMutableDictionary - objective-c

Strange things happens to me, I try to create mutable dictionary, but it return an immutable.
NSMutableDictionary * d = [NSMutableDictionary dictionary];
[d setObject:value forKey:key];
The d NSMutableDictionary is still NSDictionary, see in screenshot. And, of course, application crashes then executing [d setObject:value for:key]

Your problem is that your value is nil, and you can't set a nil value into a dictionary.
Don't worry about the __NSCFDictionary * and NSDictionary stuff that you see in the variable inspector. You most definitely have a mutable dictionary. What you see in the inspector is artifacts of how NSMutableDictionary is implemented (as a class cluster).

Related

Is a NSMutableDictionary in a NSDictionary still mutable?

The question is as simple as the title:
Is a NSMutableDictionary in a NSDictionary still mutable? Is the mdict mutable below?
NSMutableDictionary *mdict = [[NSMutableDictionary alloc] init];
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:mdict, #"key", nil];
And, is a NSDictionary in a NSMutableDictionary still immutable?
Further, what if it's array/set instead of dictionary?
Absolutely! Mutability of an object does not change when you place it into a container.
When you place a mutable dictionary into another collection, mutable or immutable, that collection adds a reference to the mutable dictionary object, but it does not change it in any other way. Same goes for placing immutable objects into collections: collections reference these objects without changing their nature.
This remains true while your object is in memory. If you serialize it and then deserialize it back, the process of deserialization may remove mutability. For example, if you save NSMutableDictionary into NSUserDefaults and then read it back, you would get back an immutable dictionary.
Yes. Objects generally don't know when they're placed into a collection, so they can't change their behavior based on that. NSDictionary does copy its keys (precisely so you can change the original object without affecting the dictionary), but it just stores a normal reference to the value.
As long as you access your variables like so
NSMutableDictionary * tempDict = [mdict objectForKey: #"Key"];
NSMutableDictionary * tempDict2 = [arrayVar objectAtIndex: index];
The temp variables retain all the functionality as before

Trying to add entries in NSArray to NSDictionary, but values in NSDictionary are getting deallocated

I have three Deck Objects which are mostly just wrappers around NSArray objects containing Card objects. Initially, all the Card objects are in deck1, and eventually move through deck2 and deck3.
I also have an NSDictionary object that maps NSString objects to Card objects. I use the cards in deck1 to build this lookup table at the beginning of the game like this...
-(NSDictionary *)buildLookupTable {
NSMutableDictionary *lookup = [[NSMutableDictionary alloc] init];
for (Card *card in self.cards) {
NSString *lookupCode = [self buildCode:card];
[lookup setObject:card forKey:lookupCode];
}
return lookup;
}
I then pass the NSDictionary and the Decks to a layer object, but when I attempt to lookup a given Card based on a NSString, I get "-[CFString hash]: message sent to deallocated instance", but I can easily find the Card I'm looking for, not deallocated, in deck1 or deck2.
The NSString I'm using as a key to retrieve the desired value isn't deallocated, nor is the NSDictionary itself. I have even iterated over the return from the NSDictionary object's allValues method, and none of those are deallocated either.
What other deallocated objects could there be?
Edit-version2:
I've narrowed it down a bit.
In the lookup code this works
NSString *key = #"4-0"; //(NSString *)sprite.userData;
return [self.cardLookup objectForKey:key];
but this doesn't
NSString *key = (NSString *)sprite.userData; // value is #"4-0"
return [self.cardLookup objectForKey:key];
In the debugger sprite.userData looks fine.
sprite.userData is defined in the Card class buildSprite method as...
sprite.userData = [NSString stringWithFormat:#"%i-%i",self.x, self.y];
It looks like calling copyWithZone on key doesn't work (like it would for any other NSString).
Found the issue!
The problem was with the sprite.userData object and how it was created.
sprite.userData = [NSString stringWithFormat:#"%i-%i",self.x, self.y];
Evidently sprite.userData was getting released (thought the Xcode debugger seemed to know what it was, possibly because I had zombie objects enabled). The correct version is...
sprite.userData = [[NSString stringWithFormat:#"%i-%i",self.x, self.y] retain];

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?

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:'

Why doesn't setValue:forKeyPath invoked on mutable dictionary throw exception for unknown keypaths?

I have following code:
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[defs setObject:[NSNumber numberWithInt:100] forKey:#"test1.test2.test3"];
[defs setValue:[NSNumber numberWithInt:10] forKeyPath:#"test2.test3.test4"];
I understand that setObject:forKey: creates association between "test1.test2.test3" key and given number object. On the other hand, setValue:forKeyPath: is Key-Value-Coding method that tries to locate object for path "test2.test3.test4", but in the end, it just silently does nothing. It doesn't even modify the dictionary!
What confuses me greatly is that setValue:forKeyPath: doesn't raise any exception nor does it report any error. Why is this? Is this behavior documented anywhere?
I can't find any documentation for this specific case, sorry, but my guess is that this:
[defs setValue:x forKeyPath:#"a.b.c"];
Is being implemented something like this:
[[defs objectForKey:#"a"] setValue:x forKeyPath:#"b.c"];
So objectForKey: returns nil, and methods called on nil just do nothing. That would explain the behaviour you have described.
NSMutableDictionary *dict = [#{#"test1" : [#{#"test2":[#{} mutableCopy]} mutableCopy]} mutableCopy];
[dict setValue:#(100) forKeyPath:#"test1.test2.test3"]; // ok
[dict setValue:#(200) forKeyPath:#"test1.test2.test4"]; // ok
[dict setValue:#(300) forKeyPath:#"test1.test21"]; // ok
[dict setValue:#(400) forKeyPath:#"test1.test22.test3"]; // fail, test22 not mutable
[dict setValue:[#{} mutableCopy] forKeyPath:#"test1.test23"]; // ok
[dict setValue:#(500) forKeyPath:#"test1.test23.test34"]; // ok