another memory management question:
I have asked this before, but did not really get an answer:
The question is would the following result in a leak or is it ok?
NSArray *txtArray = [NSArray array];
NSString *aTxtFieldTxt = [[NSString alloc]initWithString:aTxtField.text];
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSMutableString *aTxt = [[NSMutableString alloc]initWithString:aTxtFieldTxt];
[aTxtFieldTxt release];
txtArray = [aTxt componentsSeparatedByString:#" "];
aTxt = [[txtArray objectAtIndex:0] retain];
for(int i = 1; i < [txtArray count]; i++){
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
This is part of a function. And I am not sure if the assignment of aTxt = [[txtArray objectAtIndex:0] retain]; causes a leak because it is a pointer which originally points to
NSMutableString *aTxt = [[NSMutableString alloc]initWithString:aTxtFieldTxt];
[aTxtFieldTxt release];
How do I do this correctly. Would I have to use another pointer? Can somebody please explain this issue?
Thanks alot!
Lots and lots of issues with this code.
//
// Don't do this. Just declare the txtArray
//
NSArray *txtArray /* = [NSArray array]*/;
//
// You need to auto release after init in this case.
//
NSString *aTxtFieldTxt = [[[NSString alloc]initWithString:[aTxtField text]] autorelease];
//
// You are reassigning the aTxtFieldTxt and the new value is returned autoreleased.
//
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//
// Again, autorelease after init
//
NSMutableString *aTxt = [[[NSMutableString alloc]initWithString:aTxtFieldTxt] autorelease];
//
// You never alloced this instance so it needs no release.
//
/*[aTxtFieldTxt release]; */
//
// This array is returned autoreleased
//
txtArray = [aTxt componentsSeparatedByString:#" "];
//
// No need to retain here. Just get the object
//
aTxt = /*[*/[txtArray objectAtIndex:0]/* retain]*/;
for(int i = 1; i < [txtArray count]; i++)
{
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
I have found, that if you have retains/releases outside of accessors/base int/dealloc routines, you are doing something wrong. For every alloc you must have a balanced release/retain for that instance of the object. If you reassign the variable, you will loose your reference to it.
This is a quick stab on how I would write this code:
NSArray *txtArray;
NSString *aTxtFieldTxt = [NSString stringWithString:[aTxtField text]];
NSMutableString *aTxt;
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
aTxt = [NSMutableString stringWithString:aTxtFieldTxt];
txtArray = [aTxt componentsSeparatedByString:#" "];
aTxt = [NSMutableString stringWithString:[txtArray objectAtIndex:0]];
for(int i = 1; i < [txtArray count]; i++)
{
[aTxt appendString:#"+"];
[aTxt appendString:[[txtArray objectAtIndex:i]retain]];
}
Try running your application with Leaks. See if it causes a leak. Leaks is a tool in Instruments.
aTxtFieldTxt = [aTxtFieldTxt stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
This will cause the original reference to aTxtFieldTxt being lost (thus a leak). Even worse, a convenient function will return an object of retain count 0, so the -release later will crash the program.
It's better written as
// don't stuff everything into a single line.
NSCharacterSet* charsToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
// there's no need to create a copy of the string.
NSString* aTxtFieldTxt = [aTxtField.text stringByTrimmingCharactersInSet:charsToTrim];
// don't release stuff with retain count 0.
// you just want to replace all ' ' by '+' right? there's a method for that.
NSString* aTxt = [aTxtFieldTxt stringByReplacingOccurrencesOfString:#" "
withString:#"+"];
Edit: If you need to replace multiple spaces to one +, you need to parse the string, e.g.
NSCharacterSet* charsToTrim = [NSCharacterSet whitespaceAndNewlineCharacterSet];
NSString* aTxtFieldTxt = [aTxtField.text stringByTrimmingCharactersInSet:charsToTrim];
NSScanner* scanner = [NSScanner scannerWithString:aTxtFieldTxt];
[scanner setCharactersToBeSkipped:nil];
NSMutableString* aTxt = [NSMutableString string];
NSCharacterSet* whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
while (![scanner isAtEnd]) {
NSString* res;
[scanner scanUpToCharactersFromSet:whitespaceSet intoString:&res];
[aTxt appendString:res];
if ([scanner scanCharactersFromSet:whitespaceSet intoString:NULL])
[aTxt appendString:#"+"];
}
As others pointed out, this code leaks all over the place.
A real good way to find those without even running your code is the static analyzer!
Use Build and Analyze from the Build menu, and it will show you what leaks and why, with complete code paths.
Related
How do I capture the first line from a NSString object?
I currently am assigning the entire NSString object to the title of my textView, but only want to assign the first line of the string. My current code like this this:
self.textView.text = [[managedObject valueForKey:#"taskText"] description];
You want
self.textView.text = [[[[managedObject valueForKey: #"taskText"] description] componentsSeparatedByString: #"\n"] objectAtIndex:0];
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html
If you’re targeting iOS 4.0 and later, you can use -[NSString enumerateLinesUsingBlock:]:
__block NSString *firstLine = nil;
NSString *wholeText = [[managedObject valueForKey:#"taskText"] description];
[wholeText enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
firstLine = [[line retain] autorelease];
*stop = YES;
}];
self.textView.text = firstLine;
An alternative approach which is probably the most efficient and straightforward:
NSString* str = [[managedObject valueForKey:#"taskText"] description];
self.textView.text = [str substringWithRange:[str lineRangeForRange:NSMakeRange(0, 0)]];
My code below is causing my app to quit i.e. get black screen and then see in debugger console: Program received signal: “0”.
Basically it is causing problem when my orderArray has count of 2000 or more. I am using iPhone 3GS with iOS 4.2
Question: Is there a more efficient and less memory consuming way to create my long outStr?
NSString *outStr = #"";
for (int i = 0; i < count; i++) {
NSDictionary *dict = [[ARAppDelegate sharedAppDelegate].orderArray objectAtIndex:i];
outStr = [outStr stringByAppendingFormat:#"%#,%#,%#,%#\n",
[dict valueForKey:#"CODE"],
[dict valueForKey:#"QTY"],
[[ARAppDelegate sharedAppDelegate].descDict valueForKey:[dict valueForKey:#"CODE"]],
[[ARAppDelegate sharedAppDelegate].priceDict valueForKey:[dict valueForKey:#"CODE"]]];
}
Update: Thanks to very kind people who helped, below is my modified code:
NSArray *orderA = [ARAppDelegate sharedAppDelegate].orderArray;
NSDictionary *descD = [ARAppDelegate sharedAppDelegate].descDict;
NSDictionary *priceD = [ARAppDelegate sharedAppDelegate].priceDict;
NSMutableString *outStr = [[[NSMutableString alloc] init] autorelease];
for (int i = 0; i < [orderA count]; i++) {
NSDictionary *dict = [orderA objectAtIndex:i];
NSString *code = [dict valueForKey:#"CODE"];
[outStr appendFormat:#"%#,%#,%#,%#\n",
code,
[dict valueForKey:#"QTY"],
[descD valueForKey:code],
[priceD valueForKey:code]];
}
[self emailTxtFile:[NSString stringWithString:outStr]];
// This reaches end of method
The problem is that in every iteration a new string object is formed. This consumes a lot of memory. One solution could be to use a local autoreleasepool, but that's rather complicated here.
You should use an NSMutableString, like:
NSMutableString *outStr = [[[NSMutableString alloc] init] autorelease];
for (int i = 0; i < count; i++) {
NSDictionary *dict = [[ARAppDelegate sharedAppDelegate].orderArray objectAtIndex:i];
[outStr appendFormat:#"%#,%#,%#,%#\n",
[dict valueForKey:#"CODE"],
[dict valueForKey:#"QTY"],
[[ARAppDelegate sharedAppDelegate].descDict valueForKey:[dict valueForKey:#"CODE"]],
[[ARAppDelegate sharedAppDelegate].priceDict valueForKey:[dict valueForKey:#"CODE"]]];
}
Then you can use outStr, just as if it was an NSString. As Tom points out in the comments, you could turn the NSMutableString into an NSString when you're finished, using:
NSString *result = [NSString stringWithString:outStr];
[outStr release]; // <-- add this line and remove the autorelease
// from the outStr alloc/init line
making your code re-usable and easier to maintain.
I got a headache trying to count returns (\n) in my UITextView. As you'll soon realise, I'm a bloody beginner and here is my theory of what I've come up with, but there are many gaps...
- (IBAction)countReturns:(id)sender {
int returns;
while ((textView = getchar()) != endOfString [if there is such a thing?])
{
if (textView = getchar()) == '\n') {
returns++;
}
}
NSString *newText = [[NSString alloc] initWithFormat:#"Number of returns: %d", returns];
numberReturns.text = newText;
[newText release];
}
I checked other questions on here, but people are usually (in my eyes) lost in some details which I don't understand. Any help would be very much appreciated! Thanks for your patience.
You can simply
UITextView *theview; //remove this line, and change future theview to your veiw
NSString *thestring; //for storing a string from your view
int returnint = 0;
thestring = [NSString stringWithFormat:#"%#",[theview text]];
for (int temp = 0; temp < [thestring length]; temp++){ //run through the string
if ([thestring characterAtIndex: temp] == '\n')
returnint++;
}
NSArray *newlines = [textView.text componentsSeparatedByString:#"\n"];
int returns = ([newlines count]-1)
Should work. Keep in mind this isn't such a great idea if you have a gia-normous string, but it's quick, dirty and easy to implement.
there are a lot of ways to do that. Here is one:
NSString *str = #"FooBar\n\nBaz...\n\nABC\n";
NSString *tmpStr = [str stringByReplacingOccurrencesOfString:#"\n" withString:#""];
NSInteger count = [str length] - [tmpStr length];
NSLog(#"Count: %d", count);
Been running instruments on my app. Its says i am leaking 864bytes & 624bytes from 2 NSCFString and the library responsible is Foundation.
So that leads me to believe thats its not a leak caused by me? Or is it?
Here is the offending method according to instruments. It seems to be a
substringWithRange
that is leaking.
-(void) loadDeckData
{
deckArray =[[NSMutableArray alloc] init];
NSString* path = [[NSBundle mainBundle] pathForResource:#"rugby" ofType:#"txt"
inDirectory:#""];
NSString* data = [NSString stringWithContentsOfFile:path encoding:
NSUTF8StringEncoding error: NULL];
NSString *newString = #"";
NSString *newline = #"\n";
NSString *comma = #",";
int commaCount = 0;
int rangeCount = 0;
NSString *nameHolder = #"";
NSString *infoHolder = #"";
NSMutableArray *statsHolder = [[NSMutableArray alloc] init];
for (int i=0; i<data.length; i++)
{
newString = [data substringWithRange:NSMakeRange(i, 1)];
if ([newString isEqualToString: comma]) //if we find a comma
{
if (commaCount == 0)// if it was the first comma we are parsing the
NAME
{
nameHolder = [data substringWithRange:NSMakeRange(i-
rangeCount, rangeCount)];
}
else if (commaCount == 1)//
{
infoHolder = [data substringWithRange:NSMakeRange(i-
rangeCount, rangeCount)];
//NSLog(infoHolder);
}
else // if we are on to 2nd,3rd,nth comma we are parsing stats
{
NSInteger theValue = [[data
substringWithRange:NSMakeRange(i-rangeCount,rangeCount)]
integerValue];
NSNumber* boxedValue = [NSNumber
numberWithInteger:theValue];
[statsHolder addObject:boxedValue];
}
rangeCount=0;
commaCount++;
}
else if ([newString isEqualToString: newline])
{
NSInteger theValue = [[data substringWithRange:NSMakeRange(i-
rangeCount,rangeCount)] integerValue];
NSNumber* boxedValue = [NSNumber numberWithInteger:theValue];
[statsHolder addObject:boxedValue];
commaCount=0;
rangeCount=0;
Card *myCard = [[Card alloc] init];
myCard.name = nameHolder;
myCard.information = infoHolder;
for (int x = 0; x < [statsHolder count]; x++)
{
[myCard.statsArray addObject:[statsHolder
objectAtIndex:x]];
}
[deckArray addObject:myCard];
[myCard autorelease];
[statsHolder removeAllObjects];
}
else
{
rangeCount++;
}
}
[statsHolder autorelease];
}
Thanks for your advice.
-Code
As Gary's comment suggests this is very difficult to diagnose based on your question.
It's almost certainly a leak caused by you however, I'm afraid.
If you go to the View menu you can open the Extended Detail. This should allow you to view a stack trace of exactly where the leak occurred. This should help diagnose the problem.
When to release deckArray? If deckArray is a class member variable and not nil, should it be released before allocate and initialize memory space?
I'm trying to include the elements of an array in the NSString that's returned by the -description method in my class. No clue how to do this in Objective-C...in Java there's string concatenation or StringBuilder, what's the equivalent in Obj-C?
TIA..
Just use NSArray's componentsJoinedByString: method with whatever you want between them as the argument.
NSString *elementsSquishedTogether = [myArray componentsJoinedByString:#""];
NSString *connectedByACommaAndSpace = [myArray componentsJoinedByString:#", "];
If you have a C array, you can turn it into an NSArray with NSArray *converted = [NSArray arrayWithObjects:yourCArray count:yourArrayCount].
The title of your thread talks about C arrays, so here's a modification of jsumners' answer that will deal wiith C arrays.
myArray is assumed to be an ivar declared thusly:
int* myArray;
storage for myArray is assumed to be malloc'd at some point and the size of it is in an ivar declared:
int myArraySize;
The code for description goes something like
- (NSString *)description
{
NSMutableString *returnString = [[[NSMutableString alloc] init] autorelease];
for (int i = 0 ; i < myArraySize ; i++)
{
if (i > 0)
{
[returnString appendString: #", "];
}
[returnString appendFormat: #"%d", myArray[i]];
}
return [NSString stringWithFormat: #"[%#]", returnString];
}
There are variations. The above version formats the string with bracket delimiters and commas between elements. Also, it returns an NSString instead of an NSMutableString which is not a big deal, but I feel that if you say you are returning an immutable object, you probably should.
The following could should "build" a string representation of your array. Notice that it is using the -description method of the objects in the array. If you want something different you will have to make the necessary change.
- (NSString *)description: (id) myArr {
NSMutableString *returnString = [[[NSMutableString alloc] init] autorelease];
for (int i = 0, j = [myaArr count]; i < j; i++) {
[returnString appendString: [[myArr objectAtIndex: i] description]];
}
return [NSString stringWithString: returnString];
}
Edit:
As JeremyP said, I answered this using Objective-C arrays. I guess I just forgot the question when I started writing my code. I'm going to leave my answer as an alternative way to do it, though. I've also fixed the return string type from a mutable string to an immutable string (as it should be).