I have developed in C but am quite new to Objective-C and iPhone app development. I am working on an app that needs to strip the punctuation off a string. The function works but when I analyse the code it flags up some issues around one of the NSstrings I am using.
I don't understand why and therefore don't know how to fix it.
The code for the main function along with the analyser warning is:
- (IBAction)doIt {
NSString *start_punct = [[NSString alloc] init];
NSString *end_punct = [[NSString alloc] init];
NSString *actual_word = [[NSString alloc] init];
outputTextTextView.text = translatedText; //potential leak of an object alloctated on line xx and stored into 'actual word'
[translatedText release]; translatedText = nil;
[start_punct release]; start_punct = nil; //incorrect decrement of reference count of an object that is not owned at this point by the caller
[end_punct release]; end_punct = nil;
[actual_word release]; actual_word = nil; //this causes a crash
start_punct = [MainViewController getStartPunct:word start:&start_range_start len:&start_range_len];
end_punct = [MainViewController getEndPunct:word start:&end_range_start len:&end_range_len];
actual_word = [word substringWithRange: NSMakeRange(start_range_start,(end_range_start-start_range_start)+1)];
}
The code for the getStartPunct and getEndPunct functions is below
+(NSString*) getStartPunct:(NSString*) inputString
start:(NSInteger*)rangeStart
len:(NSInteger*)length {
NSString* start_str = nil;
NSRange firstAlphanumCharFromStart = [inputString rangeOfCharacterFromSet:[NSCharacterSet alphanumericCharacterSet]];
if (firstAlphanumCharFromStart.location != NSNotFound) {
start_str = [inputString substringWithRange: NSMakeRange(0, firstAlphanumCharFromStart.location)];
*length = firstAlphanumCharFromStart.length;
*rangeStart = firstAlphanumCharFromStart.location;
} //if
if (start_str == nil) {
*length=0;
*rangeStart=0;
}
return start_str;
} //getStartPunct
+(NSString*) getEndPunct:(NSString*) inputString
start:(NSInteger*)rangeStart
len:(NSInteger*)length {
NSString* end_str = nil;
NSInteger rnge = inputString.length;
NSCharacterSet* CS = [NSCharacterSet alphanumericCharacterSet];
NSRange firstNonAlphanumCharFromEnd = [inputString rangeOfCharacterFromSet:CS options:NSBackwardsSearch];
if (firstNonAlphanumCharFromEnd.location != NSNotFound) {
end_str = [inputString substringWithRange: NSMakeRange(firstNonAlphanumCharFromEnd.location+1, rnge - firstNonAlphanumCharFromEnd.location-1)];
*length = firstNonAlphanumCharFromEnd.length;
*rangeStart = firstNonAlphanumCharFromEnd.location;
} //if
if (end_str == nil) {
*length=0;
*rangeStart=0;
}
return end_str;
} //getEndPunct
Can someone see what the issue is? I'm sure it is something very basic..
Many Thanks in advance!
Thanks for all the responses so far.
adpalumbo you are right, I had paste the elements in the wrong order. The correct order is below and I have changed the initialization as suggested by Alex Nichol.
This has fixed 1 of the warning but the others (as shown below) still remain and I don't understand why 'start_punct' and 'end_punct' are behaving differently
- (IBAction)doIt {
NSString *start_punct = nil;
NSString *end_punct = nil;
NSString *actual_word = nil;
start_punct = [MainViewController getStartPunct:word start:&start_range_start len:&start_range_len]; // method returns objective with +0 retain count
end_punct = [MainViewController getEndPunct:word start:&end_range_start len:&end_range_len];
actual_word = [word substringWithRange: NSMakeRange(start_range_start,(end_range_start-start_range_start)+1)];
[translatedText release]; translatedText = nil;
[start_punct release]; start_punct = nil; //incorrect decrement of reference count
[end_punct release]; end_punct = nil;
//[actual_word release]; actual_word = nil; //possible abend
}
I'm having problems with memory leaks with this function. I thought creating an NSArray with componentsSeparatedByString was autorelease but instruments seems to indicate a leak at the NSArray aPair. Why would it indicate a leak there and not also at the other NSArrays created in the same way?
-(void) checkRequest: (NSString *)request view:(UIViewController *)theView webView:(UIWebView *)wView
{
//NSLog(#"JSResponder - checkRequest()");
NSString *aRequest = [NSString stringWithString:request];
NSArray *urlArray = [aRequest componentsSeparatedByString:#"?"];
if([urlArray count] > 1)
{
NSString *paramsString = [urlArray lastObject];
NSString *cmd = #"";
NSArray *urlParamsArray = [paramsString componentsSeparatedByString:#"&"];
int numCommands = [urlParamsArray count];
NSMutableDictionary *paramsWithNames = [[NSMutableDictionary alloc ] initWithCapacity:numCommands];
for (NSString *elementPair in urlParamsArray)
{
NSArray *aPair = [elementPair componentsSeparatedByString:#"="];
NSString *aKey = [aPair objectAtIndex:0];
NSString *aParam = [aPair objectAtIndex:1];
if([aKey compare:#"_command"] == NSOrderedSame)
{
cmd = aParam;
}
else
{
[paramsWithNames setValue: aParam forKey:aKey];
}
}
[self executeCommand: cmd withParams: paramsWithNames view:theView webView:wView];
[paramsWithNames release];
}
}
This function get called by the following:
- (void)pullJSEvent:(NSTimer*)theTimer
{
NSLog(#"MainView - pullJSEvent()");
NSString *jsCall = [NSString stringWithString:#"if(typeof checkOBJCEvents == 'function'){checkOBJCEvents();}"];
NSString *jsAnswer = [[webView stringByEvaluatingJavaScriptFromString:jsCall] retain];
if([jsAnswer compare:#"none"] != NSOrderedSame)
{
//NSLog(#" answer => %#", jsAnswer);
[jsResponder checkRequest:jsAnswer view:(UIViewController *)self webView:self.webView];
}
[jsAnswer release];
}
Thank-you
You're going to have to dig a bit deeper with the Leaks instrument. You're leaking one of the strings in the array, not the array itself. Leaks indicates that line because that's where the strings in the array are allocated.
Go into Leaks, look at a leaked instance, and click that little arrow button. You'll see all the retains and releases of the leaked object, which should point you to the problem.
I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.
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.
-(void)processGlyph:(int)glyphOne withGlyph:(int)glyphTwo
{
answer = glyphOne + glyphTwo;
NSString *tempText = [[NSString alloc] init];
tempText = [NSString stringWithFormat:#"%i",answer];
[self dispatchText:tempText];
[tempText release];
}
-(void)checkReadyToProcess
{
if (count >= 2) {
[self processGlyph:firstGlyph withGlyph:secondGlyph];
}
}
-(void)dispatchText:(NSString *) theText
{
answerText.text = theText;
}
Yes. It is here:
NSString *tempText = [[NSString alloc] init];//leaked
tempText = [NSString stringWithFormat:#"%i",answer];//creates new autoreleased object
...
[tempText release]; //causes an eventual crash
You are allocating an NSString, replacing the variable with an autoreleased NSString, and then releasing the autoreleased NSString. This will lead to a memory leak (from the original NSString) and a crash from over-releasing.
Instead, just do:
NSString *tempText = [NSString stringWithFormat:#"%i",answer];
You don't have to release it.