Is a NSMutableDictionary in a NSDictionary still mutable? - objective-c

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

Related

NSDictionary of uninitialized objects?

How can I store an uninitialized object in an NSDictionary?
I think I would do it like this, but I’m not certain that it’s a good approach:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
[MyObject1 alloc], #"myObject1",
[MyObject2 alloc], #"myObject2"
, nil];
MyObject1 *object = [dict objectForKey:#"myObject1"];
[object init];
Any help would be appreciated.
What you need is to store mutable objects inside the dictionary. Doing this you will be able to modify them after the insertion, because an immutable dictionary doesn't allow to insert a new object.
If for "uninitialized" you mean that the object has only been created with alloc, without init, that's deprecable because init may return a different object from the one returned with alloc. So just store them like you're doing it, and when you need to modify them call the accessors:
NSDictionary *dict = #{ #"myObject1" : [MyObject1 new] , #"myObject2" : [MyObject2 new] };
dict[#"myObject1"].someProperty= someValue;
If your MyObject1 class is immutable, then you have to use a mutable dictionary.

Copying a dictionary changes NSMutableArray into NSArray?

So I have this NSMutableDictionary object:
pdata=[[NSMutableDictionary alloc] initWithObjectsAndKeys:
#"",#"pid",
#"",#"pname",
[[NSMutableArray alloc] initWithCapacity:1],#"ilist",
nil];
And then I copy this object into another object like this:
NSMutableDictionary *pdataCopy=[[NSMutableDictionary alloc] initWithDictionary:pdata copyItems:TRUE];
But once Ive done this, pdataCopy.ilist is now an NSArray instead of NSMutableArray.
How can I copy a dictionary object whilst maintaining the mutability of the properies inside it?
Actually you can't. You can get a mutable array by
NSMutableArray *mutableArray = [pdataCopy.ilist mutableCopy]
You have three options:
Don't specify copyItems:YES
Scan through the dictionary after copying and replace NSArrays with NSMutableArrays (using mutableCopy) as desired.
Create your own subclass of NSMutableArray that responds to copyWithZone by producing a mutable copy of itself (and use objects of that class in your dictionary).

'-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'

- (NSMutableDictionary *)updateTemplates:(NSMutableDictionary *)oldTemplates
forSpecType:(NSString *)specType {
// oldTemplates is an NSMutableDictionary pulled from a plist
// specType is used for flexible paths, to eliminate duplicate code
// Make a dict of the parameters object (about to be overwritten)
NSMutableDictionary *parameters = [oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]];
// Dump the new data into the matching object
[oldTemplates setValue:[updateTemplates valueForKeyPath:
[NSString stringWithFormat:#"data.%#", specType]]
forKeyPath:[NSString stringWithFormat:#"root.%#", specType]];
// Put the parameters back, since they don't exist anymore
/* Instant crash, with the debugger claiming something is immutable
* But I just used the exact same method on the line above
* updateTemplates isn't immutable either; it's only when I try to mutate
oldTemplates after putting in updateTemplates -- and only the update
seems to be breaking things -- that I get the exception and crash
*/
[oldTemplates setValue:parameters forKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]];
return oldTemplates;
}
I could set up a loop to write one object of updateTemplates.specType at a time so only those parts get replaced and then I don't have to do anything with the parameters, but if it's immutable now, it will be when I try to write to it again. That won't do me any good.
If I remember correctly, dictionaries created from plists or NSUserDefaults are immutable by default. You'll have to create a mutable copy manually:
NSMutableDictionary *parameters = [[oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]] mutableCopy];
mutableCopy makes a shallow mutable copy, not a deep mutable copy. If you have an NSDictionary containing key/value pairs where the values are NSDictionary instances, mutableCopy will return a mutable dictionary containing those NSDictionary immutable instances as values.
You either need to do a deep copy or use the plist serialization functionality to decode the plist with the mutable collections option enabled. Or you could compose a new collection derived from the old.
You can simply do:
NSMutableDictionary* oldTemplates = [NSMutableDictionary dictionaryWithDictionary:[oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]]];
This will create a mutable copy from an existing NSDictionary

what's property copy means in Cocoa's Framework?(like UITabBar's items property)

In UITabBar.h, a propery signed copy
#property(nonatomic,copy) NSArray *items; // get/set visible
It's a array
And what "copy" means?
copy NSArray container obj?
copy every obj NSArray contains?
or something.
so there's a test
UITabBar* testBar = [[UITabBar alloc] init];
UITabBarItem* item = [[UITabBarItem alloc] init];
NSArray* array = [[NSArray alloc] initWithObjects:item, nil];
NSLog(#"bar:%p,%d", testBar, testBar.retainCount);
NSLog(#"item:%p,%d", item, item.retainCount);
NSLog(#"array:%p,%d", array, array.retainCount);
testBar.items = array;
NSLog(#"that item:%p,%d", [testBar.items lastObject], [[testBar.items lastObject] retainCount]);
NSLog(#"testBar.items:%p,%d", testBar.items, testBar.items.retainCount);
result
bar:0x96a9750,1
item:0x96aa230,2
array:0x96aa280,1
that item:0x96aa230,2
testBar.items:0x96aa280,6
why neither container array nor obj in array has been "copied"?
Two things:
collection -copy is always shallow. It doesn't copy the collections elements (In fact, nothing guarantees that these elements are even copyable – i.e. are conforming to NSCopying protocol). This explains why obj is not copied – it doesn't get any extra retain.
Foundation tries to optimizes its implementation of -copy to -retain whenever is possible. For example, -[NSString copy] is a retain for immutable strings. Since collection copies are shallow, the same optimization works for immutable collections. That's why array is not copied but just retained.
The reason the copy has not been made in this case is that NSArray is immutable. You do not need to make a copy of it to guard against changes to the array, because such changes cannot be made; it is sufficient to retain the same immutable array.
If you try this experiment with NSMutableArray, you will get a different result.

Assigning values to Instance variables in Objective C

The function I'm looking at:
-(void)viewDidLoad {
NSBundle *bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"statedictionary" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.statesZips = dictionary;
[dictionary release];
NSArray *components = [self.stateZips allKeys];
NSArray *sorted = [components sortedArrayUsingSelector:#selector(compare:)];
self.States = sorted;
NSString *selectedState = [self.states objectAtIndex:0];
NSArray *array = [stateZips objectForKey: selectedState];
self.zips = array;
}
Why is an NSDictionary allocated, then assigned to a pointer called *dictionary, and then assigned to the instance variable stateZips? Why not allocate it and assign it directly to the instance variable and save memory of creating and releasing another NSDictionary? The same methodology is always followed, including later in this function with the NSArray...
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
self.statesZips = dictionary;
[dictionary release];
Also, this sorting puts the keys from a hash table (dictionary) in alphabetical order. I'm not sure I understand this line:
NSArray *sorted = [components sortedArrayUsingSelector:#selector(compare:)];
No one seems to have addressed the fact that the line
self.statesZips = dictionary;
is not directly an instance variable assignment. stateZips is a property, and so that line of code calls the setStateZips: method. That method retains or copies the dictionary, so unless the viewDidLoad method intends to use it again for some purpose, it's not needed any longer. That makes it OK to release it.
The previous line:
[[NSDictionary alloc] initWithContentsOfFile:plistPath];
allocates an object. That makes it your responsibility to release it once you don't need it any more. After assigning it to the statesZips property, it's no longer needed, so it's released and you shouldn't use dictionary any more. You'll notice that later code only refers to self.stateZips, not dictionary.
In the case of the NSArray later in the method, viewDidLoad does not allocate the object, so that method is not responsible for calling release on it. The rule of thumb is that if you alloc it, you're responsible for making sure it gets released. Otherwise, it's not your problem.
Sorting the array uses the sortedArrayUsingSelector: method. A selector identifies a method in Objective-C. And the #selector is the literal syntax for selectors (kind of like how #"" is the literal syntax for NSString objects). So, what that code says, is "give me an array where the objects in components are sorted, and use the compare: method to compare each object when you do the sort. When it sorts the array, it will call compare: on the objects in the array to determine how to put them in order.
The statesZips property is probably retained, that's the reasoning.
When the NSDictionary is first allocated, its retain count is 1. When it's assigned to statesZips, the retain count becomes 2. When it's released, the retain count drops to 1, which is usually the desired outcome.
Note that the code below would have produced (almost) the same result:
self.statesZips = [NSDictionary dictionaryWithContentsOfFile:plistPath];
because dictionaryWithContentsOfFile returns an autoreleased object.
As a convention, class methods like [NSDictionary dictionary] return autoreleased objects (which automatically get released after some time), while the usual alloc-init method (as in [[NSDictionary alloc] init]) return retained objects.
I suggest you read the Memory Management Programming Guide for Cocoa for further information.
EDIT: I must have missed the last part of your question when I first read it, but Barry has already answered that part.
This code uses reference-counted memory management (not the automatic garbage collection memory management available in Objective-C 2.0 on OS X). When any object (in this case, the NSDictionary and the NSArray) are alloc'd, the caller is responsible for calling -release on that instance. Failing to call release causes a memory leak. The code could have been written as
self.statesZips = [[[NSDictionary alloc] initWithContentsOfFile:plistPath] autorelease];
but at the expense of less explicit memory management (relying on NSAutoreleasePool to release the alloc'd instance at the end of the event loop iteration.
the call
[components sortedArrayUsingSelector:#selector(compare:)];
returns an array of whose elements come from components but according to the return value of calling [elem1 compare:elem2] to compare two array elements.