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.
Related
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
Hello i have this function, that gets dummy data from file in project:
Issues are shown in those lines:
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"]; - potential leak of an object.
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data]; - potential leak of an object stored into 'jsonParser'
for (id key in statisticsResponse) { - potential leak of an object stored into 'statisticArray'
for (int i = 0; i < statsForDate.count; i++) { - potential leak of an object stored into 's'
if (self.statistics==nil)
{
self.statistics = [[NSMutableDictionary alloc]init];
NSString *path = [[NSBundle mainBundle] pathForResource: #"StatisticsDataJSON" ofType: #"TXT"];
NSError *error = nil;
NSString *data = [NSString stringWithContentsOfFile: path
encoding: NSUTF8StringEncoding
error: &error];
//NSLog(#"%#",data);
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *statisticsResponse = [jsonParser objectWithString:data];
for (id key in statisticsResponse) {
NSArray *statsForDate = [statisticsResponse objectForKey:key];
NSMutableArray *statisticArray = [[NSMutableArray alloc]init];
for (int i = 0; i < statsForDate.count; i++) {
Statistic *s = [[Statistic alloc]init];
s.locationId = [[statsForDate objectAtIndex:i] objectForKey:#"locationId"];
int value =[[[statsForDate objectAtIndex:i] objectForKey:#"visits"] integerValue];
s.visits = value;
value =[[[statsForDate objectAtIndex:i] objectForKey:#"totalUsers"] integerValue];
s.totalUsers = value;
value= [[[statsForDate objectAtIndex:i] objectForKey:#"uploads"] integerValue];
s.uploads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"downloads"] integerValue];
s.downloads = value;
value = [[[statsForDate objectAtIndex:i] objectForKey:#"apps"] integerValue];
s.apps = value;
[statisticArray addObject:s];
}
[self.statistics setObject:statisticArray forKey:key];
};
}
I have found that autorelease in ststisticsResponse - resolves the problem:
NSMutableDictionary *statisticsResponse = [[jsonParser objectWithString:data]autorelease];
But then something fails in SBJsonStreamParserAccumulator.m in dealoc function.
What is the problem?
Note that the warnings about potential leaks come on the line after the potential leak, because that's the first point at which the referenced object is technically "leaked". So your current fixes are probably over-releasing and causing crashes.
The first statement in your question actually refers to a leak in this line, immediately before:
self.statistics = [[NSMutableDictionary alloc]init];
You've got no further reference to that allocated dictionary, and it's a retained property, so you have a leak.
self.statistics = [[[NSMutableDictionary alloc]init] autorelease];
Will fix that one. The next one, you have to release the jsonParser when you've finished with it (after the parsing is done):
[jsonParser release];
I'm not going to go through all of them but you should get the idea. Basically you need to read the memory management guide, or update to ARC.
Pay attention to the variable names in the warnings. They tell you where the leak is.
Please someone tell me why retaining NSMutabledictionary after some operations is ok, but retain on creation gives a leak.
-(void) Func2
{
NSString *_errorDesc;
NSPropertyListFormat format;
NSString *_plistPath = [[NSBundle mainBundle] pathForResource:#"List" ofType:#"plist"];
NSData *_plistData = [[NSFileManager defaultManager] contentsAtPath:_plistPath];
NSDictionary *dataDict = (NSDictionary *) [NSPropertyListSerialization
propertyListFromData:_plistData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&_errorDesc];
for (int i = 0; i<[dataDict count]; i++)
{
[_ups setObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:0],[NSNumber numberWithInt:0], nil]forKey:[NSString stringWithFormat:#"%d",i]];
}
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:_ups forKey:#"Ups"];
[defaults synchronize];
}
-(void) Func1
{
_ups = [NSMutableDictionary dictionary];
//_ups = [[NSMutableDictionary dictionary]retain]; = leak
//_ups = [[NSMutableDictionary alloc]init]; = leak
if(![[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"])
{
[self Func2];
}else{
_ups = [[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"];
}
[_ups retain]; // - ok
}
Instruments->Leaks shows that leak detected only when I trying to retain on creation, but if I retain after filling a dictionary all fine.
Thanks.
Looking at just one of the if paths (the second) reduces to this:
_ups = [[NSMutableDictionary alloc]init];
// Returns a retained instance assigned to _ups.
_ups = [[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"];
// Now a new instance is assigned to "_ups` without releasing the first instance.
[_ups retain];
//The above retain is incorrect since the method name does not start with "new" or have "copy" in it. This implies that the returned value should be autoreleased. Review Objective-C naming conventions. See Apple's Objective-C Conventions.
All in all, best practice is to use ARC. With ARC the first instance would have been automatically released.
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];
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.