views:

212

answers:

5

Hello, my app crashes often in this for-loop:

for (int a = 0; a <= 20; a++) {
        NSString * foo = [[NSString alloc]initWithString:[[newsStories objectAtIndex:arc4random() % [newsStories count]] objectForKey:@"title"]];
        foo = [foo stringByReplacingOccurrencesOfString:@"’" withString:@""];
        foo = [[foo componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@" "];
        foo = [foo stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
        textView.text = [textView.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", foo]];
    }

The code changes a NSString from a NSDictionary and adds it into a textView. Sometimes it first crashes in the second time using the for-loop. What did I wrong?

A: 

Try declaring NSString *foo before the loop. Something like this:

NSString *foo;
for(int a = 0; a <= 20; a++)
{
foo = [NSString stringWithString:[some code here]];
.
.
.
[do something to foo]
}

I've run into errors a couple of times when not doing this.

Dustin Pfannenstiel
Why would this matter if foo is not used outside the loop?
Brandon Bodnár
Strange: With this code I got five for-loop runs and it didn't crashes. Then it crashes. I started the program again and now it crashes immediately.
Flocked
I don't have a good answer for you. Yet. This is a solution that has worked for me several times.One thing that does worry me is that foo isn't being released a the end of the loop. The property setting of the text field should handle memory management of foo once the property is set. By using the class method "stringWithString:" you should get the memory managmement under control with minimal changes to your code.
Dustin Pfannenstiel
What's the crash?
Dustin Pfannenstiel
Strage, funny and great - Now it works and I don't know why.
Flocked
What changed in your code?
Dustin Pfannenstiel
A: 

Actually, declare the NSString in the loop does no matter and it is good to control object in the loop domain. I found there was a memory leak in your code that the nsstring was no longer to be released after you created. So, firstly, you should control the nsstring object and make sure there is no memory leak, after you alloc it, it was your responsibility to release it. Secondly I'm not sure what in your array named "newsStories", would you please upload some code to show some detail about this array? Such as the count number of this array or the items in the array.

A: 

Why are you using NSString? That's an immutable class which means you can't change it and yet you change it multiple times. Try using NSMutableString.

regulus6633
The given code does not actually mutate the NSString object, but rather reassigns `foo` to point at a new autoreleased NSString object (leaving no pointer to the previously-used string objects).
Isaac
Isaac, could you explain that to me then? When he executes:foo = [foo stringByReplacingOccurrencesOfString:@"’" withString:@""];how is that NOT mutating the string foo? I've always created an new string in that case like this:NSString *foo2 = [foo stringByReplacingOccurrencesOfString:@"’" withString:@""];
regulus6633
A: 

I'm assuming you're working in a reference-counted environment--that is, garbage collection is off.

First, try changing the first line inside the loop to:

    NSString * foo = [NSString stringWithString:[[newsStories objectAtIndex:arc4random() % [newsStories count]] objectForKey:@"title"]];

Using the class method stringWithString: instead of alloc and initWithString: yields an autoreleased NSString instead of a string with retain count = 1. This should solve the memory leak mentioned in the comments and/or other answers.

However, each foo = operation inside the loop creates another autoreleased NSString, so in each iteration through the loop, you're creating 3 (your version) or 4 (if you make the above change) autoreleased strings, which could build up over the iterations and cause trouble. Because of this, it might help to create and release an autorelease pool inside your loop:

for (int a = 0; a <= 20; a++) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSString * foo = [NSString stringWithString:[[newsStories objectAtIndex:arc4random() % [newsStories count]] objectForKey:@"title"]];
    foo = [foo stringByReplacingOccurrencesOfString:@"’" withString:@""];
    foo = [[foo componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@" "];
    foo = [foo stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    textView.text = [textView.text stringByAppendingString:[NSString stringWithFormat:@"%@ ", foo]];
    [pool release];
}
Isaac
+2  A: 

initWithString: raises an exception if you pass it a nil argument, so if your newsStories dictionary item happens to be missing its title, that will cause a crash (unless you're catching the exception elsewhere).

Try splitting off the part that retrieves the title and make sure it's non-nil before passing it to initWithString:

NSString *titleString = [[newsStories objectAtIndex:arc4random() % [newsStories count]] objectForKey:@"title"];

if (!titleString)
    titleString = @"<TITLE IS EMPTY>";

foo = [[[NSString alloc] initWithString: titleString] autorelease];

Alternatively, if the newsStories dictionary item's title object isn't an NSString instance, that would crash initWithString: as well.

tedge