In the Beginning iPhone 4 book, the author has this code to create a category for creating a deep copy of an NSDictionary that has an NSArray of names for each letter of the alphabet to show an example of an indexed table with a search bar.
#import "NSDictionary-MutableDeepCopy.h"
#implementation NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy];
else if ([oneValue respondsToSelector:#selector(mutableCopy)]) oneCopy = [oneValue mutableCopy];
if (oneCopy == nil)
oneCopy = [oneValue copy];
[returnDict setValue:oneCopy forKey:key];
[oneCopy release];
}
return returnDict;
}
#end
Can someone explain the for loop logic? I'm not sure what he's trying to do in seeing which value responds to which selector, and why it would be added to the dictionary. Thanks.
So, the for loop simply iterates through all the keys in the dictionary. Beforehand, we create a new dictionary called returnDict - this will be what we return.
For each key in the dictionary we want to copy, we...
Get the object stored for that key ([self valueForKey:key]), and save it into a variable called oneValue.
If oneValue implements our mutableDeepCopy method (ie, it's an NSDictionary) go call it, and assign the return value into a variable called oneCopy.
Else, we see if oneCopy implements the mutableCopy method. If it does, we put the output into the oneCopy variable.
At this point, we check to see if following steps (2) and (3) the oneCopy variable has had anything assigned to it (if (oneCopy == nil)). If it doesn't (ie, it's equal to nil) we can assume the object doesn't implement either mutableDeepCopy or mutableCopy, so we instead call a plain old copy and assign its value to oneCopy.
Add oneCopy into our returnDict dictionary using the original key.
That's the for loop - at the end of it all, we go and return the copied dictionary.
The logic in the for-loop is convoluted because the author is trying to get as mutable and as deep a copy of the entire array as possible. The code tries three different ways to satisfy this, in order of preference:
Use mutableDeepCopy if possible (if the object understands that message).
Otherwise, use mutableCopy if possible.
If all else fails, just use copy.
If the object is just plain not copiable, your code goes boom when it sends the object -copy, since no test is made for whether the object responds to -copy. This is appropriate, since trying to deep copy an array containing items that cannot be copied is definitely programmer error.
Related
I'm writing a custom xml deserializer for an iphone app. As you can see below, I'm looping through all the list elements in the xml, I have debugged it, and with each loop there is a new and different element. The problem is that the xpath helper methods (there are similar ones to the one posted below, but for int and decimal) always returns the same value.
For example - 1st loop's xml "SomeValue" value will be "abc" and the helper method will return "abc", second item comes around and its xml "SomeValue" is "XYZ", but the helper method will still return "abc".
I'm new to iphone/objective c/memory managment so it could be any number of things. I just cant determine what the problem is :( could someone please offer some help?
-(void) deserializeAndCallback:(GDataXMLElement *)response
{
NSError * error;
NSArray *listings = [response nodesForXPath:#"//ListingSummary" error:&error];
NSMutableArray *deserializedListings = [[NSMutableArray alloc] init];
//loop through all listing elements, create a new listing object, set its values, and add
//it to the list of deserialized objects.
if(listings.count > 0)
{
for (GDataXMLElement *listingElement in listings)
{
Listing *list = [[Listing alloc] init];
//xpath helper function (shown below), just to get the value out of the xml
list.someValue = [QuickQuery getString:listingElement fromXPath:#"//SomeValue"];
[deserializedListings addObject:list];
}
}
if([super.delegate respondsToSelector: #selector(dataReady:)])
{
[super.delegate dataReady:deserializedListings];
}
}
+(NSString *) getString:(GDataXMLElement *)element fromXPath:(NSString *)xPath
{
NSError *error;
NSArray *result = [element nodesForXPath:xPath error:&error];
if(result.count > 0)
{
GDataXMLElement *singleValue = (GDataXMLElement *) [result objectAtIndex:0];
return singleValue.stringValue;
[singleValue release];
}
return nil;
[error release];
[result release];
}
EDIT: ok... I found a bit more info. Inside the helper function, the nodesForXpath method returns all the nodes from the entire xml, not just the current element I'm busy with. Does GDataXMLElement keep reference to its parent elements at all?
Example of what the xml looks like
<ListingSummary>
<SomeValue>abc</SomeValue>
</ListingSummary>
<ListingSummary>
<SomeValue>jhi</SomeValue>
</ListingSummary>
<ListingSummary>
<SomeValue>xyz</SomeValue>
</ListingSummary>
What you are seeing is correct behaviour for the XPath query you are using. You actually want a query relative to the current node, not the root of the document as you are doing.
See http://www.w3schools.com/xpath/
BTW + (NSString *)getString:(GDataXMLElement *)element fromXPath:(NSString *)xPath is a class method, not a static method.
You say that nodesForXPath: returns all the nodes from the whole document. Since you are calling that method with the same argument, #"//SomeValue", every loop, you get back the same array every time. This means that [result objectAtIndex:0] gives you the same object every time.
Also, as I mentioned in a comment, you should not be releasing singleValue, error, or result in your helper method. You don't own those and you're not responsible for their memory. On the other hand, since you create list using alloc, you do need to release it at the end of each loop; you are currently leaking a Listing object every pass.
It looks OK for me. Although the releases inside the getString:fromXPath: aren't necessary (you don't need to release parameters entered into a method or objects obtained from a NSArray. The proper way to release an object from a NSArray is removing it from the array, as for objects passed as a parameter, if you want to release it you should do it after you call the method.
The problem to your question must be somewhere else.
result.count should be [result count] since count is a method and not a property of NSArray
so here's my function
+ (void) AddAddress:(NSString*)ip:(NSString*)mac {
NSMutableDictionary* currentAddresses = [[NSMutableDictionary alloc] initWithContentsOfFile:[self filePath:ADDRESSES_FILE]];
[currentAddresses setObject:mac forKey:ip];
[Configuration Write:currentAddresses];
[currentAddresses release];
}
[self filePath:ADDRESSES_FILE] returns a string containing the full path of a file (ADDRESSES_FILE) located in the Documents. The file may not exist, in this case I want to just write an object to the dictionary and then write it down
the problem is that when i do setObject forKey it doesn't add anything to the dict (if i just do alloc init to the dict, it works fine, but i want to read the records from the file, if there are any, and replace/add a record by using the AddAddress function)
First thing first, you'd rather respect naming convention of ObjC to be more easily understood... For instance, method names start with a small case and it's better to put a descriptive name before the colons for each arguments, a method name like that: AddAddress:: isn't really good...
Now, about loading a dictionary from a file, the thing is that if the file can't be loaded (doesn't exist, wrong format,...), the returned object will be nil, the simplest way to correct that is to put:
if(currentAddresses == nil) currentAddresses = [[NSMutableDictionary alloc] init];
I was running Leaks tool and discovered a massive leak in my Dictionary mutableDeepCopy but I can't figure out what's wrong with the code. Any suggestions?
#interface RootViewController : UIViewController{
NSDictionary *immutableDictionary;
NSMutableDictionary *mutableDictionary;
}
Here is the line of code that's highlighted in Instruments
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
Here is the method for creating a mutable copy of a Dictionary
#interface NSDictionary(MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy;
#end
Here is method implementation, I've highlighted the code that Leaks saids is leaking 100%
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *dictionaryToReturn = [NSMutableDictionary dictionaryWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for(id key in keys) {
id value = [self valueForKey:key];
id copy = nil;
if ([value respondsToSelector:#selector(mutableDeepCopy)]) {
copy = [value mutableDeepCopy];
} else if ([value respondsToSelector:#selector(mutableCopy)]) {
copy = [value mutableCopy]; //This is the Leak
}
if (copy == nil) {
copy = [value copy];
}
[dictionaryToReturn setValue:copy forKey:key];
}
return dictionaryToReturn;
}
You need to analyse this in light of Apple's Memory Management Rules.
Starting with this line:
self.mutableDictionary = [self.immutableDictionary mutableDeepCopy];
I would expect mutableDeepCopy to return an object I own, so at some point I need to release or autorelease it. e.g.
NSMutableDeepCopy* temp = [self.immutableDictionary mutableDeepCopy];
self.mutableDictionary = temp;
[temp release];
or
self.mutableDictionary = [[self.immutableDictionary mutableDeepCopy] autorelease];
So now we need to look at mutableDeepCopy. Because it has 'copy' in the name it needs to returned an "owned" object which, in practice means "forgetting" to release the returned object. You have already failed to do that when you create the returned object in the first line, since dictionaryWithCapacity: gives you an object you do not own. Replace it with
NSMutableDictionary *dictionaryToReturn = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
Now you own it.
It is important that you make your mutableDeepCopy obey the rules because it means you can treat the objects returned from mutableDeepCopy, mutableCopy and copy in exactly the same way. In all three cases you own the object copy that you insert into the array. Because you own it, you must release it or it'll leak as you found out. So, at the end of the loop, you need
[copy release];
That'll stop the leak.
How is your property declared? If is is retain or copy, then this doesn't leak.
Your problem is that the name mutableDeepCopy suggests that it returns a retained object, and not an autoreleased one as it actually does.
Edit:
And at the mutableDeepCopy itself, you need to release the copy variable after adding to the dictionary.
mutableCopy increments the retain count of the object, as does setValue:forKey:. This means that when dictionaryToReturn is dealloc'ed, the object that had mutableCopy called still has a retain count of one.
Try doing this instead:
copy = [[value mutableCopy] autorelease];
i have created NSMutableDictionary with 10 keys.Now i want to access NSMutableDictionary keys in a same order as it was added to NSMutableDictionary (using SetValue:* forKey:* );
How can i achieve that ?
If you absolutely must use a dictionary container, you have to use a key that is sortable by the order in which you add key-value pairs. Thus, when creating your dictionary, you use a key that is an auto-incrementing integer or similar. You can then sort on the (integer) keys and retrieve the values associated with those keys.
If you do all of that, however, you may as well just use an NSMutableArray and add values to the array directly! It will be much faster and require less code. You just retrieve objects in order:
for (id obj in myArray) { /* do stuff with obj... */ }
NSMutableDictionary can't do that. Take a look at e.g. Matt Gallaghers OrderedDictionary.
I wrote a quick method to take a source array (of objects that are all out of order) and a reference array (that has objects in a desired (and totally arbitrary) order), and returns an array where the items of the source array have been reorganized to match the reference array.
- (NSArray *) reorderArray:(NSArray *)sourceArray toArray:(NSArray *)referenceArray
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [referenceArray count]; i++)
{
if ([sourceArray containsObject:[referenceArray objectAtIndex:i]])
{
[returnArray addObject:[arrReference objectAtIndex:i]];
}
}
return [returnArray copy];
}
Note that this is very fragile. It uses NSArray's containsObject: method, which ultimately will call NSObject's isEqual:. Basically, it should work great for arrays of NSStrings, NSNumbers, and maybe NSDates (haven't tried that one yet), but outside of that, YMMV. I imagine if you tried to pass arrays of UITableViewCells or some other really complex object, it would totally sh*t itself, and either crash or return total garbage. Likewise if you were to do something like pass an array of NSDates as the reference array and an array of NSStrings as the source array. Also, if the source array contains items not covered in the reference array, they'll just get discarded. One could address some of these issues by adding a little extra code.
All that said, if you're trying to do something simple, it should work nicely. In your case, you could build up the reference array as you are looping through your setValue:forKey:.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (//whatever you are looping through here)
{
[yourDictionary setValue://whatever forKey:key];
[referenceArray addObject:key];
}
Then, when you want to loop over your items in the order they came in, you just
for (NSString *key in [self reorderArray:[myDict allKeys] toArray:referenceArray])
Actually you have a reference array in order manner then why you have to add to one more array.So i guess this approach is not good.Please consider my opinion.
Although #GenralMike 's answer works a breeze, it could be optimized by leaving off the unnecessary code as follows:
1) Keep an array to hold reference to the dictionary keys in the order they are added.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (id object in someArray) {
[yourDictionary setObject:object forKey:someKey];
[referenceArray addObject:someKey]; // add key to reference array
}
2) Now the "referenceArray" holds all of the keys in order, So you can retrieve objects from your dictionary in the same order as they were originally added to the dictionary.
for (NSString *key in referenceArray){
//get object from dictionary in order
id object = [yourDictionary objectForKey:key];
}
I'm assuming my understanding of how to perform a deep copy isn't just there yet. The same with some sub-optimal memory handling that I'm performing down below. This code below probably depicts a shallow copy, and I believe that's where my problem might be. I have some cookie-cutter code for an example that looks like the following:
NSArray *user = [[xmlParser createArrayWithDictionaries:dataAsXML
withXPath:kUserXPath] retain];
if([user count] > 0) {
self.name = [[user valueForKey:#"name"] copy];
}
// Crash happens if I leave the next line un-commented.
// But then we have a memory leak.
[user release];
[xmlParser release];
Unfortunately when I comment out [user release], the code works, but we have an obvious memory leak. The method createArrayWithDictionaries:withXPath: was refactored last night when the SO community helped me understand better memory management. Here's what it looks like:
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [CXMLDocument alloc];
theXMLDocument = [theXMLDocument initWithXMLString:xmlDocument
options:0
error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary;
attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue]
forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return dictionaries;
}
I'm guessing there's a couple of issues that might be going on here:
Auto release on my dictionaries array is happening, thus my app crashing.
I'm not performing a deep copy, only a shallow copy. Thus when the user array is released, self.name is done for.
With NSZombieEnabled, I see the following:
*** -[CFString respondsToSelector:]:
message sent to deallocated instance 0x1ae9a0
Also, the final call where the backtrace shows this is crashing contains the following code in a separate module from the other two methods:
User *u = self.user;
NSString *uri = [NSString stringWithFormat:#"%#/user/%#/%#",
[self groupName], u.userId, kLocationsUri];
Between all the auto releasing/copies/retain happening between the client code and createArrayWithDictionaries:withXPath, I'm a bit confused as to the real problem here. Thanks again for helping me understand.
OK, you don't need to retain the return value from createArrayWithDictionaries: since you're not keeping it around. The return value is autoreleased. I'd strongly recommend reading up on how autoreleasing works. You only retain things that you intend to keep around in your object.
Also, user is an NSArray. If you call [user valueForKey:#"name"], you'll get another NSArray of values representing the values of the name key for each of the objects in users. Furthermore, how is the name property on your object defined? If you declared it as copy or retain (I believe retain is the default if you don't specify it yourself), you don't need to copy or retain the value. Indeed, the accessor should always be responsible for doing the memory management, not the caller. If you wrote your own accessor (i.e. you didn't use the #synthesize keyword), you need to make sure you do the memory management there.
I'm guessing what you meant to write was something more like this:
NSArray *user = [xmlParser createArrayWithDictionaries:dataAsXML withXPath:kUserXPath];
if ([user count] > 0)
self.name = [[user objectAtIndex:0] objectForKey:#"name"];
[xmlParser release];
I think your troubles are stemming from a misunderstanding of how memory management works in Objective-C.
Hope this helps.
Auto release on my dictionaries array is happening, thus my app crashing.
If the caller intends to keep the array around somewhere, it needs to retain it. Otherwise, it will crash when it tries to access the (now-deceased) object.
If the caller is going to store it in a property, it must use the self.dictionaries = […] syntax, not dictionaries = […]. The former is a property access, which calls the setter method; the latter is a direct instance variable assignment.
Coming back to your actual question, that of a deep copy: You need to get the sub-elements of every element and put them in each element's dictionary.
Basically, you need a recursive method (or a queue, but that's harder—file under premature optimization until you've proven you need it) that takes an element and returns a dictionary, and then you need to call this method on each of your element's child elements, and collect the results into an array and put that into the dictionary you're creating.
I would recommend making this recursive method an instance method of the element. Something like:
- (NSDictionary *) dictionaryRepresentation {
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
NSArray *childElements = [self childElements];
return [NSDictionary dictionaryWithObjectsAndKeys:
attributeDictionary, #"attributes",
[childElements valueForKey:#"dictionaryRepresentation"], #"childElements",
nil];
}
Then you replace the loop in createArrayWithDictionaries:withXPath: with a similar valueForKey: message. I'll leave you to fill it in.
valueForKey: is Key-Value Coding's principal method. In both places, we're making use of NSArray's handy implementation of it.
(If the use of valueForKey: still doesn't make sense to you, you should read the KVC Programming Guide. KVC is vitally important in modern Cocoa, so you do need to read this sooner or later.)