Why would the following implementation of the Dictionary cause a memory leak? See the screenshot below as well. Practically all of the leaks there are from this method.
- (void) setLocation:(NSString *) location:(NSString *) turnPage {
NSLog(#"Start setLocation");
//---get the path to the property list file---
NSString *localPlistFileNameConf = [[self documentsPath] stringByAppendingPathComponent:#"Config.plist"];
NSMutableDictionary *copyOfDict;
//---if the property list file can be found---
if ([[NSFileManager defaultManager] fileExistsAtPath:localPlistFileNameConf]) {
//---load the content of the property list file into a NSDictionary object---
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:localPlistFileNameConf];
//---make a mutable copy of the dictionary object---
copyOfDict = [dict mutableCopy];
[dict release];
}
else {
//---load the property list from the Resources folder---
NSString *pListPath = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:pListPath];
//---make a mutable copy of the dictionary object---
copyOfDict = [dict mutableCopy];
[dict release];
}
location = [self checkLocationValidity:location:turnPage];
[copyOfDict setValue:location forKey:#"Location"];
[self writeConfigToFile:copyOfDict];
NSLog(#"End setLocation");
}
You're not releasing copyOfDict anywhere. You own any object created with a method that starts with copy, so you need to release those objects. It's probably misreporting the source as the original dictionary due to a bit of trickery in the NSDictionary class cluster for efficiency reasons. Try running analyse over your code, it should point these things out to you.
Related
The usual method for loading data from a dictionary contained in a plist is as below:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
NSDictionary *data= [NSDictionary dictionaryWithContentsOfFile:path];
Is there a way to import only the element(s) specified in a key / set of keys, like:
NSDictionary *data= [NSDictionary dictionaryWithContentsOfFile:path forKey:key];
The idea is to perform lazy loading of dictionary contents by key.
So based on my comment above, you could add a class method to the NSDictionary via a category. You could do something like (not tested BTW).
+ (NSDictionary *)dictionaryWithContentsOfFile:(NSString *)path forKeys:(NSArray *)keys
{
NSMutableDictionary *newDictionary = nil;
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
if (dictionary) {
newDictionary = [NSMutableDictionary dictionary];
for (id key in dictionary.allKeys) {
if ([keys containsObject:key]) {
newDictionary[key] = dictionary[key];
}
}
}
return [newDictionary copy];
}
If you did this, you'd see your spike in memory, but it should subside once dictionary is freed.
Alternatively, take a look at YAJL (https://github.com/lloyd/yajl). I've used this when dealing with very large JSON files. This was mainly the stream it in chunks. It is event driven, so you should be able to stream it in and detect the keys you want (hopefully).
please try the below method.
- (void)viewDidLoad
{
NSMutableArray *arry;
arry = [[NSMutableArray alloc]initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Catalog" ofType:#"plist"];
NSDictionary *dic = [menuArray objectAtIndex:indexPath.row];
lblName.text = [dic objectForKey:#"MenuName"];
}
I have to retain the score and current date for a game in a plist . I created a dictionary with two arrays:
- (void)viewDidLoad
{
[super viewDidLoad];
dateArray=[[NSMutableArray alloc]init];
scoreArray=[[NSMutableArray alloc]init];
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
[self getDate];
NSString *path = [[NSBundle mainBundle] pathForResource:#"score" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
int lastItemIndex = scoreArray.count;
[scoreArray insertObject:[dict objectForKey:#"scoreElements"] atIndex:lastItemIndex];
}
-(IBAction) saveData
{
NSString *plistPath =[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"Score.plist"];
NSMutableArray* plistDict = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
[plistDict setValue:self.scoreLabel.text forKey:#"scoreElements"];
[plistDict writeToFile:plistPath atomically: YES];
}
This code is good only for the last game played, but I want to fill the tableView with the old scores as well. How can I do it?
Instead of using
[scoreArray insertObject:[dict objectForKey:#"scoreElements"] atIndex:lastItemIndex];
why don't you use
[scoreArray addObject: dict["scoreElements"];
Also when you are saving using below code, it will always set new value to "scoreElements" overriding old ones.... so you have to use array as well.
[plistDict setValue:self.scoreLabel.text forKey:#"scoreElements"];
You could use NSUserDefaults to store stuff and retrieve it later on.
Check out the reference about this class at Apple :
nsuserdefault apple
I am developing iPhone app in which i have TableView,
What i want to do is On click of UITableViewCell i want to remove this values from plist, MOBILES.Brand.2 and MOBILES.Brand.5 if they exist in .plist if they doesn't exist in .plist then i want to add it to my .plist
Here is the structure of my .plist:
:
Here is my code snippet:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
plistPath = [self getPlistPath];
//PlistDict is mutableDictionary contains the keys and values of .plist (of above image)
if ([PlistDict valueForKeyPath:#"MOBILES.Brand.2"] != nil) { //if key and it's value exists in Filter.plist
//Delete Key from .plist...
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
[savedStock removeObjectForKey:#"MOBILES.Brand.2"];
[savedStock removeObjectForKey:#"MOBILES.Brand.5"];
[savedStock writeToFile:plistPath atomically:YES];
}else{
//Add Key to .plist
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
[data setValue:#"251" forKeyPath:#"MOBILES.Brand.2"];
[data setValue:#"298" forKeyPath:#"MOBILES.Brand.5"];
[data writeToFile:plistPath atomically:YES];
}
}
-(NSString*)getPlistPath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Filter.plist"];
PlistDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
return path;
}
after writing above code snippet it doesn't remove the keys from .plist but it adds the key successfully if not existed.
Where i am doing mistake ? please help and thanks in advance.
The problem is you are trying to remove using the NSMutableDictionary keypath directly using removeObjectForKey that requires the exact key not keypath and you are supplying a keypath. Use the following NSMutableDictionary Category taken from here
#interface NSMutableDictionary (Additions)
- (void)removeObjectForKeyPath: (NSString *)keyPath;
#end
#implementation NSMutableDictionary (Additions)
- (void)removeObjectForKeyPath: (NSString *)keyPath
{
// Separate the key path
NSArray * keyPathElements = [keyPath componentsSeparatedByString:#"."];
// Drop the last element and rejoin the path
NSUInteger numElements = [keyPathElements count];
NSString * keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
// Get the mutable dictionary represented by the path minus that last element
NSMutableDictionary * tailContainer = [self valueForKeyPath:keyPathHead];
// Remove the object represented by the last element
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
#end
It should work for you.
Method - 2
If the above doesn't work, you can do an iteration of your dictionary, and specifically delete the object for the key.. Try
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
NSMutableDictionary *brand=[[savedStock objectForKey:#"MOBILES"] objectForKey:#"Brand"];
[brand removeObjectForKey:#"2"];
[brand removeObjectForKey:#"5"];
Cheers.
I am using this code to get book names from a config.plist file. However my memory management is problematic. The '[dict release]' breaks the app completely and it exits.
The code works when the '[dict release]' is removed but it causes memory leaks as far as I can tell.
bnames is a global NSMutableArray
What am I doing wrong?
- (NSString *)loadBookname: (NSInteger) bookToLoad {
bookToLoad = [self bookOrder:bookToLoad];
//---get the path to the property list file---
plistFileNameConf = [[self documentsPath] stringByAppendingPathComponent:#"Config.plist"];
//---if the property list file can be found---
if ([[NSFileManager defaultManager] fileExistsAtPath:plistFileNameConf]) {
//---load the content of the property list file into a NSDictionary object---
dict = [[NSDictionary alloc] initWithContentsOfFile:plistFileNameConf];
bnames = [dict valueForKey:#"BookNames"];
[dict release];
}
else {
//---load the property list from the Resources folder---
NSString *pListPath = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
dict = [[NSDictionary alloc] initWithContentsOfFile:pListPath];
bnames = [dict valueForKey:#"BookNames"];
[dict release];
}
plistFileNameConf = nil;
NSString *bookNameTemp;
bookNameTemp = [bnames objectAtIndex:bookToLoad - 1];
NSLog(#"bookName: %#", bookNameTemp);
return bookNameTemp;
}
You need to allocate your array properly:
bnames = [[NSArray alloc] initWithArray:[dict valueForKey:#"BookNames"]];
Double check that your dict returns the right data type.
There does not appear to be anything wrong with the way you allocate NSDictionary (although you could also use the [NSDictionary dictionaryWithContentsOfFile:] and save yourself having to worry about the release.
Either way I would suggest the issue is not with the [release] but probably the line BEFORE release:
bnames = [dict valueForKey:#"BookNames"];
a) Where is that allocated. I don't see an allocation or declaration of it anywhere?
b) What type of value do you expect back?
Put a break point on it and make sure your getting what you expect or anything.
If dict is not already a strong property, make it one. Then, use self.dict when assigning to it (and keep the release).
I've found what appears to be a better solution to the issue. This lets iOS manage the memory.
//---finds the path to the application's Documents directory---
- (NSString *) documentsPath {
NSLog(#"Start documentsPath");
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
// NSLog(#"Found documentsPath 40");
NSLog(#"End documentsPath");
return documentsDir;
}
- (NSString *) configPath {
NSLog(#"Start configPath");
NSString *plistFileNameConf = [[self documentsPath] stringByAppendingPathComponent:#"Config.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistFileNameConf]) {
plistFileNameConf = [[NSBundle mainBundle] pathForResource:#"Config" ofType:#"plist"];
}
NSLog(#"plistFile: %#",plistFileNameConf);
NSLog(#"End configPath");
return plistFileNameConf;
}
The following calls the above code as necessary:
NSString *Choice;
NSArray *properties;
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:[self configPath]];
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp) {
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
Choice = [temp objectForKey:#"Choice"];
properties = [temp objectForKey:Choice];
This is my code: (customNames and customNamesArray are static variables)
-(void) loadCustomDataFromDisk
{
NSString *fullPath = [self filePathAndFileName: #"customData.plist"];
if ( ![[NSFileManager defaultManager] fileExistsAtPath: fullPath] )
{
NSLog(#"Loading file fails: File not exist");
customNames = [[NSMutableDictionary alloc] init];
customNamesArray = [[NSMutableArray alloc] init];
}
else
{
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] initWithContentsOfFile: fullPath];
customNames = [customItems objectForKey: #"customNamesDict"];
customNamesArray = [customItems objectForKey: #"customNamesArray"];
if (!customItems)
NSLog(#"Error loading file");
[customItems release];
}
}
-(void) saveCustomDataToDisk
{
NSString *path = [self filePathAndFileName: #"customData.plist"];
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] init];
[customItems setObject: customNames forKey: #"customNamesDict"];
[customItems setObject: customNamesArray forKey: #"customNamesArray"];
BOOL success;
success = [customItems writeToFile:path atomically:YES];
if (!success)
NSLog(#"Error writing file: customDataDict.plist");
[customItems release];
}
According to Build and Analyze, I have a potential leak in loading customItems
NSMutableDictionary *customItems = [[NSMutableDictionary alloc] initWithContentsOfFile: fullPath];
true enough, according to Instruments, I do have a leak in that part. But when I tried release or autoreleasing customItems, my app crashes. Even if I change NSMutableDictionary to NSDictionary, I still have the leak.
How do I fix this?
Any help would be very much appreciated. :) Thanks :)
You have to retain customNames and customNamesArray because you are using reference from dictionary customItems and after passing reference you are releasing it.
customNames = [[customItems objectForKey: #"customNamesDict"] retain];
customNamesArray = [[customItems objectForKey: #"customNamesArray"] retain];
Now you can release customItems.
Your code is right as I can see. You can see answer here and may be it helps - Leak problem with initWithContentsOfFile
I have only one question: You create NSString *fullPath and never release it. Is it autoreleased string? If so - your code is fine.