Objective C - NSString - Memory basics - objective-c

I am trying to return an NSString that has been initialized from a plist.
If I comment out my release lines this code works. I would however like to release these objects from memory as I no longer need them.
I thought that 'initWithString' would copy the contents of the target string into my new string meaning I could safely release the NSMutableArray. But it isn't. Why not?
+ (NSString*) genImage {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Images" ofType:#"plist"];
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSLog(#"%d", [arrayOfImages count]);
int indexToLoad = 0;
NSString *res = [[NSString alloc] initWithString:[arrayOfImages objectAtIndex:indexToLoad] ];
[arrayOfImages release];
[path release];
return res;
}

You do not retain the return value of -[NSBundle pathForResource:ofType:] (the path variable), so there is no need to release it (and doing so will cause a crash, most likely). However, you should autorelease res, as you do retain that. You can change your last line to
return [res autorelease];

Related

Memory leak on NSString stringByAppendingPathComponent:

Analyze is showing memory leak where I assign filePath value for fileB in the following code snippet:
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docsDir stringByAppendingPathComponent:#"/fileA"];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]){
self.propertyA = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
} else {
// initialize array with default values and write to data file fileA
[self populatePropertyAForFile:filePath];
}
filePath = [docsDir stringByAppendingPathComponent:#"/fileB"];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]){
self.propertyB = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
} else {
// initialize array with default values and write to data file fileB
[self populatePropertyBForFile:filePath];
}
I understand it is because the previous value (for fileA) has not been released. But I can't figure-out how to stop this leak.
No. There is nothing wrong with filePath. The problem is almost certainly with your two properties, propertyA and propertyB. If they are retain properties, then the arrays you assign to them and their contents will leak because you own the arrays you alloc and you are not releasing them. Change the lines like this:
self.propertyA = [[[NSMutableArray alloc] initWithContentsOfFile:filePath] autorelease];
// ^^^^^^^^^^^ will release ownership of the array

NSArray release crashes app

I am trying to "tokenize" my data that I get from my text file.
When I am doing this, I get an error like this:
malloc: * error for object 0x844c730: pointer being freed was not allocated
* set a breakpoint in malloc_error_break to debug
The code I use looks like this:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"mydata" ofType:#"txt"];
NSString *rawText = [NSString stringWithContentsOfFile:filePath encoding:NSASCIIStringEncoding error:nil];
//No error was caused by above line
NSArray *tmp = [rawText componentsSeparatedByString:#"####"];
NSString *title = #"";
NSString *detail = #"";
for(int i = 0; i < [tmp count]-1; i++)
{
NSArray *base = [[tmp objectAtIndex:i] componentsSeparatedByString:#"##"];
title = [[NSString alloc] initWithFormat:#"%#$$%#",title,[base objectAtIndex:0]];
detail = [[NSString alloc] initWithFormat:#"%#$$%# | %# | %#",
title,
[base objectAtIndex:0],
[base objectAtIndex:1],
[base objectAtIndex:2]
];
[base release];
}
[tmp release];
It must be this part of the code, since if I comment this piece out, it works fine.
Reading the error it says set a breakpoint which I have no idea to put that in malloc_error_break
What is wrong in my memory management doing?
Or else how can I split up the string in some other way?
You got tmp from componentsSeparatedByString:. Since that selector doesn't start with "alloc" or "new" or "copy" or "mutableCopy", and since you didn't do [tmp retain], you don't own tmp. So you shouldn't do [tmp release].
Same for base.
Base and temp are autorelease objects so you should not release that objects.
You don't have to release base. It's already autoreleased.
You are trying to release array base without allocating it.
NSArray *base = [[tmp objectAtIndex:i] componentsSeparatedByString:#"##"]; didn't allocate memory for base.You don't need [base release];
until it is allocated.

iPad app crashing with no crash log while reading from file

The basic structure of my program has the user select an item from a UITableView, which corresponds to a stored text file. The file is then read into an array and a dictionary, where the array has the keys (I know I can just get the keys from the dictionary itself, this isn't my question).
The view is then changed to a UISplitView where the master view has the keys, and the detail view has the items in the dictionary attributed to that key. In this case, it's a series of "Yes/No" questions that the user selects the answer to.
My problem is this: When I click on a cell in the UITableView (first screen), it works fine, the data is read in perfectly, and so on. When I go back to the UITableView and click on the same cell again, the program crashes. Here is the read-in-from-file method:
-(NSArray *)readFromFile:(NSString *)filePath{
// NSLog(#"Path was: %#", filePath);
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
// NSLog(#"File was: %#", file);
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
/**
This is where the fun stuff happens!
**/
while(![fileScanner isAtEnd]){
//Scan the string into held
[fileScanner scanUpToString:#"\r" intoString:&held];
NSLog(#"Inside the while loop");
// If it is a character, it's one of the Key points, so we do the management necessary
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 2: %#", [checkers objectAtIndex:2]);
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
}
NSLog(#"After if statement");
key = [checkers objectAtIndex:2];
[keys addObject:(NSString *) key];
detailStrings = [[NSMutableArray alloc] init];
}
else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 1: %#", [checkers objectAtIndex:1]);
[detailStrings addObject:[checkers objectAtIndex:1]];
}
}
NSLog(#"File has been read in");
[details setObject:detailStrings forKey:key];
NSArray *contents = [[NSArray alloc] initWithObjects:(NSMutableArray *) keys, (NSMutableDictionary *) details, nil];
[detailStrings release];
return contents;
}
I've determined that the program crashes inside the
if(detailStrings != nil)
statement. I figure this is because I'm missing some memory management that I am supposed to be doing, but don't have the knowledge of where it's going wrong. Any ideas as to the problem, or why it is crashing without giving me a log?
detailStrings is not initialized when you enter the while loop. When you declare NSMutableArray *detailStrings; inside a method, detailStrings is not automatically set to nil. So when you do
if ( detailStrings != nil ) { .. }
it enters the if statement and since it is not initialized, it will crash when you access detailStrings.
Another thing is that detailStrings won't be initialized if it enters the else part of the loop first. That will cause a crash too. So based on your requirement, either do
NSMutableArray *detailStrings = nil;
or initialize it before you enter the while loop.
Deepak said truth. You should initialize detailStrings with nil first.
But there is second possible issue:
I recommend also to set nil after release, because in the next loop you may test nonexistent part of memory with nil.
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
detailStrings = nil;
}
And the third possible issue: depending from incoming data you may go to the second part of IF statement first time and try to addObject into non-initialized array.
The fourth (hope last): you have memory leak with "checkers" arrays
Here's what I'm seeing:
//read in the file
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
//create the scanner
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
//declare some uninitialized stuff
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
//initialize some stuff
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
//begin loop
while(![fileScanner isAtEnd]){
//scan up to a newline
[fileScanner scanUpToString:#"\r" intoString:&held];
//see if you scanned a lowercase string
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
//make an array
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
//do a check... against an uninitialized value
if(detailStrings != nil){
//set a potentially uninitialized value into an array with an uninitialized key
[details setObject:detailStrings forKey:key];
At this point, you're pretty much hosed.
The fix:
properly initialize your variables
run the static analyzer
read the memory management programming guide

release of previously deallocated object issue

I have a function which use for read one single line from a csv file.
But I got a release of previously deallocated object error, or sometimes the it is "double free" error.
I try to track down which object causes this error base on the error memory address, but I failed to do this.
Here's the code:
#interface CSVParser : NSObject {
NSString *fileName;
NSString *filePath;
NSString *tempFileName;
NSString *tempFilePath;
//ReadLine control
BOOL isFirstTimeLoadFile;
NSString *remainContent;
}
#property(nonatomic,retain) NSString *fileName;
#property(nonatomic,retain) NSString *filePath;
#property(nonatomic,retain) NSString *tempFileName;
#property(nonatomic,retain) NSString *tempFilePath;
#property(nonatomic,retain) NSString *remainContent;
-(id)initWithFileName:(NSString*)filename;
-(BOOL)checkAndCopyFile:(NSString *)filename;
-(BOOL)checkAndDeleteTempFile;
-(NSString*)readLine;
-(NSArray*)breakLine:(NSString*)line;
#end
#implementation CSVParser
#synthesize fileName;
#synthesize filePath;
#synthesize tempFileName;
#synthesize tempFilePath;
#synthesize remainContent;
-(id)initWithFileName:(NSString *)filename{
//ReadLine control
isFirstTimeLoadFile = TRUE;
self.fileName = filename;
self.tempFileName = [[NSString alloc] initWithFormat:#"temp_%#",fileName];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
self.filePath = [documentDir stringByAppendingPathComponent:fileName];
self.tempFilePath = [documentDir stringByAppendingPathComponent:tempFileName];
if ([self checkAndCopyFile:fileName]) {
return self;
}else {
return #"Init Failure";
}
}
-(BOOL)checkAndCopyFile:(NSString *)filename{
BOOL isFileExist;
NSError *error = nil;
NSFileManager *fileManger = [NSFileManager defaultManager];
isFileExist = [fileManger fileExistsAtPath:filePath];
if (isFileExist) {
//Create a temp file for reading the line.
[fileManger copyItemAtPath:filePath toPath:tempFilePath error:&error];
return TRUE;
}else {
return FALSE;
}
}
-(NSString*)readLine{
NSError *error = nil;
//Read the csv file and save it as a string
NSString *tempFirstLine = [[[NSString alloc] init] autorelease];
NSString *stringFromFileAtPath = [[NSString alloc] init];
if (isFirstTimeLoadFile) {
NSLog(#"Into First Time");
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
isFirstTimeLoadFile = FALSE;
}else {
NSLog(#"Not First Time");
NSLog(#"Not First Time count:%d",[remainContent retainCount]);
stringFromFileAtPath = remainContent;
remainContent = nil;
}
if ([stringFromFileAtPath isEqualToString:#""]) {
[stringFromFileAtPath release];
return #"EOF";
}
//Get the first line's range
NSRange firstLineRange = [stringFromFileAtPath rangeOfString:#"\n"];
//Create a new range for deletion. This range's lenght is bigger than the first line by 1.(Including the \n)
NSRange firstLineChangeLineIncludedRange;
if (stringFromFileAtPath.length > 0 && firstLineRange.length == 0) {
//This is the final line.
firstLineRange.length = stringFromFileAtPath.length;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange = firstLineRange;
}else {
firstLineRange.length = firstLineRange.location;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange.location = firstLineRange.location;
firstLineChangeLineIncludedRange.length = firstLineRange.length + 1;
}
//Get the first line's content
tempFirstLine = [stringFromFileAtPath substringWithRange:firstLineRange];
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange withString:#""];
[stringFromFileAtPath release];
error = nil;
return tempFirstLine;
}
And the following code shows how I use the class above:
CSVParser *csvParser = [[CSVParser alloc] initWithFileName:#"test.csv"];
BOOL isFinalLine = FALSE;
while (!isFinalLine) {
NSString *line = [[NSString alloc] init];
line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
[line release];
}
[csvParser release];
If I run the code, and finish the csv parsing, the App's main function will give me the double free error when it try to free the autorelease pool."* __NSAutoreleaseFreedObject(): release of previously deallocated object (0x6a26050) ignored"
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
Could someone help me solve this issue?
Thank you!
[pool release];
Do not use -retainCount.
The absolute retain count of an object is meaningless.
You should call release exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).
See the Memory Management Guidelines for full details.
There are a few problems in your code:
you aren't following the correct init pattern. You should have a self = [super init...]; if (self) {...} in there somewhere.
tempFileName is a retain property and you assign it the result of alloc/init. It will be leaked.
An immutable empty string ([[NSString alloc] init]) is pretty much never useful. And, in fact, stringFromFileAtPath is being leaked (technically -- implementation detail wise there is an empty immutable singleton string and thus, no real leak, but.... still...)
Finally, the crash: your readLine method correctly returns an autoreleased object. Yet, your while() loop consuming the return value of readLine is also releaseing that return value, leading to a double-release and an attempt to free that which was already freed.
You should "build and analyze" your code. I bet the llvm static analyzer would identify most, if not all, of the problems I mentioned above (and probably some more I missed).
When building with the analyzer, do you have either "all messages" or "analyzer issues only" selected in the Build window? Because, looking at the code, I'm surprised the analyzer didn't catch the obvious problem with stringFromFileAtPath.
Excerpting the code, you have the following lines that manipulate stringFromFileAtPath:
NSString *stringFromFileAtPath = [[NSString alloc] init];
....
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
....
stringFromFileAtPath = remainContent;
....
[stringFromFileAtPath release];
And remainContent is set by:
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange
withString:#""];
You are releasing an autoreleased object. By memory keeps going up, how are you measuring it? Don't use Activity Monitor as it is nearly as useless to developers as retainCount is misleading. Use Instruments.
Your tempFirstLine NSString object is declared with autorelease, and is returned as your NSString line, which is then released.
Try using this:
while (!isFinalLine) {
NSString *line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
}
Replac this:
NSString *stringFromFileAtPath = [[NSString alloc] init];
with this:
NSString *stringFromFileAtPath = nil;
and get rid of the [stringFromFileAtPath release] statements.
The first line creates a pointer to a new string object that you never use, because you immediately overwrite the pointer with a pointer to string objects from elsewhere, which you don't need to release because you don't own them/didn't create them. Since you are releasing them, you're getting a crash.
You make the same mistake with tempFirstLine.

Problem in memory manegment?

I developing an application, in which i working with database manipulation. The method i have written in database class as follows.
-(NSMutableArray *)getData: (NSString *)dbPath{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK){
NSString *sqlQuery = [NSString stringWithFormat:#"SELECT empID, addText FROM Employee WHERE nameID = %#", nameID];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, [sqlQuery UTF8String], -1, &selectstmt, NULL) == SQLITE_OK){
while (sqlite3_step(selectstmt) == SQLITE_ROW){
[dataArray addObject:[[NSMutableDictionary alloc] init]];
[[dataArray lastObject] setObject:[NSString
stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[[dataArray lastObject] setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
}
}
sqlite3_finalize(selectstmt);
}
sqlite3_close(database);
return dataArray;
}
The above code work fine on the simulator but cannot on device.
I also was tracing the memory leaks , in which i founding that memory leak in above method code. But i not able to solve the that memory leak.
Now i also find out memory leak in following method.
(id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes
{
if ((self = [super init]))
{
_buffer = [str mutableCopy];
_attributes = [NSMutableArray arrayWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
}
return self;
}
The leak near _buffer = [str mutableCopy];. And leak trace gives me in the output continuous increasing NSCFString string allocation. How i maintain it?
Thanks in advance.
Your leak is that you don't release either the dataArray object, nor the mutable dictionaries you create in the while loop. Consider autoreleasing the mutable array, and manually releasing the dictionaries after you add them to the array.
As for why it "doesn't work" on the device, you need to be more specific about what happens and why that isn't what you expect.
Your inner loop leaks the NSMutableDictionary objects, as you should release them after adding to the array, i.e.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSString stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[dict setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
[dataArray addObject:dict];
[dict release];
Also, your whole method should most probably return an autoreleased object by naming convention. Not sure if this is a leak - depends on how you call that method and if you release the returned value.
So maybe use
return [dataArray autorelease];
From the first glance you have 2 places where leaks can be:
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
return dataArray;
Caller method is responsible for releasing array returned from your method - check if it does.
Also your method name is not consistent with obj-c guidelines - they suggest that methods returning non-autoreleased object( so caller is responsible for releasing them) should contain create, alloc, copy in their name. So it could be better to return autoreleased array (return [dataArray autorelease]; from this method and let caller decide whether it needs to retain array or not.
Second place is
[dataArray addObject:[[NSMutableDictionary alloc] init]];
It is leaking dictionary object, you should probably just write
[dataArray addObject:[NSMutableDictionary dictionary]];
Your method contains two call to +alloc that don't have corresponding calls to -release or -autorelease.
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
[dataArray addObject:[[NSMutableDictionary alloc] init]];
You can rewrite these lines like this to get rid of the leak:
NSMutableArray *dataArray = [NSMutableArray array];
...
[dataArray addObject:[NSMutableDictionary dictionary]];