Memory leak in NSMutableArray, can't autorelease - objective-c

I am having trouble releasing tempArray below... tempArray is a leak and I have tried return [tempArray autorelease] and it causes a crash. Does anyone know how to get rid of the memory leak in tempArray?
+(NSMutableArray*) returnTheArray:(NSString*)thePath forTheKey:(NSString*)theKey {
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0];
NSString *testString = [thePath stringByAppendingString:#".plist"];
plistPath = [rootPath stringByAppendingPathComponent:testString];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
plistPath = [[NSBundle mainBundle] pathForResource:thePath ofType:#"plist"];
}
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&errorDesc];
if (!temp) {
CCLOG(#"Error reading plist: %#, format: %d", errorDesc, format);
}
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:[temp objectForKey:theKey]];
return tempArray;
}

Well, the problem is that returnTheArray is not a name that the Analyzer will recognize as returning a retained value -- this is why it complains. So either you rename the method or return an autoreleased value. But if the latter you need to make sure that the "consumer" of the returned value appropriately deals with it -- retaining it if the value must persist beyond the next autorelease pool drain operation.

try:
NSMutableArray *tempArray = [NSMutableArray arrayWithArray:[temp objectForKey:theKey]];

Related

Writing NSArray into plist

I'm trying to save a NSMutableArray into a pre-existing plist.
When I try:
NSError *errorDesc;
NSPropertyListFormat format;
NSString *plistPath;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
plistPath = [rootPath stringByAppendingPathComponent:#"Array_Label_Generiche.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
NSLog(#"prima volta");
plistPath = [[NSBundle mainBundle] pathForResource:#"Array_Label_Generiche" ofType:#"plist"];
}
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSMutableArray *temp = (NSMutableArray *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&errorDesc];
if (!temp) {
NSLog(#"Error reading plist: %#, format: %lu", errorDesc, format);
}
contatore = (int)temp.count ;
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
[dictionary setObject:array_copiato forKey:[NSString stringWithFormat: #"%#", [alertView textFieldAtIndex:0].text]];
NSDictionary *dictionary_da_salvare = [[NSDictionary alloc ] initWithDictionary: dictionary];
//AGGIUNGO I DATI CREATI
NSLog(#"TEMP %#",temp);
[temp addObject:dictionary_da_salvare];
BOOL didWriteArray = [temp writeToFile:plistPath atomically:YES];
if (didWriteArray)
{
NSLog(#"Write to .plist file is a SUCCESS!");
}
else
{
NSLog(#"Write to .plist file is a FAILURE!");
}
In this case, output show me that "Write to .plist file is a FAILURE!".
In the Apple Guide, I read that I must serialize data before writing on the plist.
Then I tried to write these lines of code:
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:temp format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc];
if(plistData == nil){
NSLog (#"error writing to file: %#", errorDesc);
}
When I run the application, the output area reads:
error writing to file: Property list invalid for format (property
lists cannot contain objects of type 'CFType') Write to .plist file
is a FAILURE!
I think because the array test is NSMutableArray because I have read in the Apple Guide:
The NSPropertyListSerialization class provides methods that convert
property list objects to and from several serialized formats. Property
list objects include NSData, NSString, NSArray, NSDictionary, NSDate,
and NSNumber objects. These objects are toll-free bridged with their
respective Core Foundation types (CFData, CFString, and so on).

NSInvalidArgumentException [NSDictionary initWithObjects:forKeys:]: count of objects (0) differs from count of keys (1)

Got the error NSInvalidArgumentException [NSDictionary initWithObjects:forKeys:]: count of objects (0) differs from count of keys (1). I used following code for
NSString *errorDesc = nil;
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *jsonDictionary = [parser objectWithString:responseData];
paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
documentsPath = [paths objectAtIndex:0];
plistPath = [documentsPath stringByAppendingPathComponent:#"json_keys.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects:jsonDictionary, nil] forKeys:[NSArray arrayWithObjects: #"json_keys", nil]];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc];
if (plistData)
{
[plistData writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", errorDesc);
[errorDesc release];
}
Judging from error message, [parser objectWithString:responseData] returns nil in this case. So, [NSArray arrayWithObjects:jsonDictionary, nil] creates empty array. After that, you are trying to create NSDictionary, passing empty array as values and array with #"json_keys" string as keys. So, error message is pretty accurate in this case.
Fix for this situation depends on what you are trying to achieve.
You can simply return from your method and don't do anything in case, when jsonDictionary is nil:
// ...
NSDictionary *jsonDictionary = [parser objectWithString:responseData];
if (nil == jsonDictionary) {
return;
}
paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// ...
Another possibility is storing empty plistDict for this case:
NSMutableDictionary *plistDict = [NSMutableDictionary dictionaryWithCapacity:1];
if (nil != jsonDictionary) {
[plistDict setObject:jsonDictionary forKey:#"json_keys"];
}
Of course, you can also find out why [parser objectWithString:responseData] returns nil and fix that.

How to save data locally in app? [duplicate]

This question already has answers here:
Working with data in iOS Apps (What to choose? NSData, CoreData, sqlite, PList, NSUserDefaults)
(2 answers)
Closed 9 years ago.
I've been struggling with this for ages now and I really need some good help here. :)
I have an app where I'm parsing a quite big JSON into appdelegate's didFinishLaunchingWithOptions.
My Model Objects are:
Tab:
NSString *title
NSMutableArray *categories
Category:
NSString *title
NSMutableArray *items
Item
NSString *title
NSString *description
UIImage *image
I need to save the data locally, cause the parsing takes about 15 seconds every time my app starts. I'm using the SBJSON framework.
Here's my code for parsing:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"json_template" ofType:#"json"];
NSString *contents = [NSString stringWithContentsOfFile: filePath encoding: NSUTF8StringEncoding error: nil];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *json = [jsonParser objectWithString: contents];
tabs = [[NSMutableArray alloc] init];
jsonParser = nil;
for (NSString *tab in json)
{
Tab *tabObj = [[Tab alloc] init];
tabObj.title = tab;
NSDictionary *categoryDict = [[json valueForKey: tabObj.title] objectAtIndex: 0];
for (NSString *key in categoryDict)
{
Category *catObj = [[Category alloc] init];
catObj.name = key;
NSArray *items = [categoryDict objectForKey:key];
for (NSDictionary *dict in items)
{
Item *item = [[Item alloc] init];
item.title = [dict objectForKey: #"title"];
item.desc = [dict objectForKey: #"description"];
item.url = [dict objectForKey: #"url"];
if([dict objectForKey: #"image"] != [NSNull null])
{
NSURL *imgUrl = [NSURL URLWithString: [dict objectForKey: #"image"]];
NSData *imageData = [NSData dataWithContentsOfURL: imgUrl];
item.image = [UIImage imageWithData: imageData];
}
else
{
UIImage *image = [UIImage imageNamed: #"standard.png"];
item.image = image;
}
[catObj.items addObject: item];
}
[tabObj.categories addObject: catObj];
}
[tabs addObject: tabObj];
}
What is the best way of doing this? Using Core Data or NSFileManager?
If you have som code example too it will make me very happy.
This is the last thing i need to fix before the app is ready for app store and it just kills me! I can't solve this problem.
If you are working on iOS then you save a file to the Documents folder. On Mac OS X it would be in the Application Support folder. Since you are on iOS, read this answer for how to access the Documents folder.
All of the objects that you want to store should implement NSCoding. The above variables already do. Should you want to store the tabs, categories and items directly they would need to implement NSCoding. Then all you need is to serialize them to a file. When opening you app you can look for this file and get your objects back without parsing.
The code should look something like this (untested and error checking is ommited for brevity):
- (void) saveStateToDocumentNamed:(NSString*)docName
{
NSError *error;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths[0] stringByAppendingPathComponent:docName];
if ([fileMan fileExistsAtPath:docPath])
[fileMan removeItemAtPath:docPath error:&error];
// Create the dictionary with all the stuff you want to store locally
NSDictionary *state = #{ ... };
// There are many ways to write the state to a file. This is the simplest
// but lacks error checking and recovery options.
[NSKeyedArchiver archiveRootObject:state toFile:docPath];
}
- (NSDictionary*) stateFromDocumentNamed:(NSString*)docName
{
NSError *error;
NSFileManager *fileMan = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [paths[0] stringByAppendingPathComponent:docName];
if ([fileMan fileExistsAtPath:docPath])
return [NSKeyedUnarchiver unarchiveObjectWithFile:docPath];
return nil;
}

Objective C - fatal error

I have a problem with my Xcode app, When I push a button, my app crashes.
Here is my button's action, I also declared variables, strings, etc... but it isn't in this code:
{
NSLog(#" - Writing Data.plist Labels");
NSString *error;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"Data.plist"];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects:
[NSArray arrayWithObjects: compteur01, compteur02, compteur03, compteur04, compteur05, compteur06, nil]
forKeys:[NSArray arrayWithObjects: #"name1", #"name2", #"name3", #"name4", #"name5", #"name6", nil]];
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error];
if(plistData) {
[plistData writeToFile:plistPath atomically:YES];
}
else {
NSLog(#"Error writeToFile:plistData:labels");
[error release];
}
}
Aparently compteur01 is nil.
Most likely the other compteur0x are nil too. But for this message at least the first one is nil.
Make sure that none of the objects added to the dictionary are nil. Use NSNULL instead if you need them to represent a nil/null value.

Can't figure out about saving files

I am trying to save my object to the file system on an iPad, but I seem to be doing something wrong. Here is how I have archived the object:
NSString *localizedPath = [self getPlistFilePath];
NSString *fileName = [NSString stringWithFormat:#"%#.plist", character.infoName];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:character];
fileName = [fileName stringByReplacingOccurrencesOfString:#" " withString:#"_"];
localizedPath = [localizedPath stringByAppendingPathComponent:fileName];
NSLog(#"File Path: %#", localizedPath);
if(data) {
NSError *writingError;
BOOL wasWritten = [data writeToFile:localizedPath options:NSDataWritingAtomic error:&writingError];
if(!wasWritten) {
NSLog(#"%#", [writingError localizedDescription]);
}
}
Now, this creates a plist file that I can see and read on the file system. When I try to use the following to unarchive it though:
NSError *error;
NSString *directory = [self getPlistFilePath];
NSArray *files = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:directory error:&error];
NSMutableArray *characters = [[NSMutableArray alloc]init];
for(NSString *path in files) {
if(![path hasSuffix:#"plist"]) {
continue;
}
NSString *fullPath = [directory stringByAppendingPathComponent:path];
NSData *data = [NSData dataWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data]; // get EXEC_BAD_ACCESS here...
[data release];
[characters addObject:object];
}
I get an EXEC_BAD_ACCESS error.
The IRSkillsObject conforms to the NSCoding protocol. You can see, I commented the line that I get the error on.
I am sure it's something I am doing wrong, but I just can't see it. I have tried to step through with the debugger (placing a break point in the initWithCoder: method of the object) but I don't get any errors then. In fact, it places the data in the object properly as I watch. But once it's done loading the data, it gives the error. I have tried using the retain method, but that doesn't help.
Any help that you can provide would be greatly appreciated!
You are releasing data without allocating it.
NSData *data = [NSData dataWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[data release];
So try this:
NSData *data = [[NSData alloc] initWithContentsOfFile:fullPath];
IRSkillsObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
[data release];
When an EXEC_BAD_ACCESS error is found. Usually is because some data has been released but it is still needed in the code.
Maybe there is a property inside your IRSkillsObject not retained in -initWithCoder: