Background processing gives me BAD ACCESS crash? - iphone

I followed this guide here to doing background processing:
http://evilrockhopper.com/2010/01/iphone-development-keeping-the-ui-responsive-and-a-background-thread-pattern/
And the only 1 line of code which was put to background processing was:
sound = flite_text_to_wave([cleanString UTF8String], voice);
But for some reason i got a bad access signal.
Debugging it shows that it crashes on that line too. This is the code I now have in that part. Bear in mind that most of this is just default Flite stuff from the sfoster project which has had no problems before, when it was all together, not separated into 3.
-(void)speakText:(NSString *)text //This is called by my app
{
cleanString = [NSMutableString stringWithString:#""];
if([text length] > 1)
{
int x = 0;
while (x < [text length])
{
unichar ch = [text characterAtIndex:x];
[cleanString appendFormat:#"%c", ch];
x++;
}
}
if(cleanString == nil)
{ // string is empty
cleanString = [NSMutableString stringWithString:#""];
}
//The next line i've put in from the link
[self performSelectorInBackground:#selector(backgroundTextToSpeech) withObject:nil];
}
-(void)backgroundTextToSpeech {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//The following line is the one it crashes on
sound = flite_text_to_wave([cleanString UTF8String], voice);
[self performSelectorOnMainThread:#selector(backToForegroundTextToSpeech) withObject:nil waitUntilDone:YES];
[pool release];
}
-(void)backToForegroundTextToSpeech {
NSArray *filePaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *recordingDirectory = [filePaths objectAtIndex: 0];
// Pick a file name
tempFilePath = [NSString stringWithFormat: #"%#/%s", recordingDirectory, "temp.wav"];
// save wave to disk
char *path;
path = (char*)[tempFilePath UTF8String];
cst_wave_save_riff(sound, path);
// Play the sound back.
NSError *err;
[audioPlayer stop];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:tempFilePath] error:&err];
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
}
Any ideas what i've done wrong/what i can do differently to stop this happening?
EDIT: A picture of the debugger after changing it round with some code posted below:

first thing that jumps out:
your class needs to hold a retain count for cleanString.
typically, this is accomplished via a retained (or copied, which is generally preferable with NSStrings and other concrete/immutable types) property:
#interface MONSpeaker : NSObject
{
NSString * cleanString;
}
#property (copy) NSString * cleanString;
#end
#implementation MONSpeaker
#synthesize cleanString;
/* ... */
- (void)dealloc
{
[cleanString release], cleanString = nil;
[super dealloc];
}
-(void)speakText:(NSString *)text // This is called by my app
{
NSMutableString * str = [NSMutableString stringWithString:#""];
if([text length] > 1)
{
int x = 0;
while (x < [text length])
{
unichar ch = [text characterAtIndex:x];
[str appendFormat:#"%c", ch];
x++;
}
}
if(str == nil) // why not check for nil at creation instead?
{ // string is empty
str = [NSMutableString stringWithString:#""];
}
self.cleanString = str;
// The next line i've put in from the link
[self performSelectorInBackground:#selector(backgroundTextToSpeech) withObject:nil];
}
-(void)backgroundTextToSpeech {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// The following line is the one it crashes on
sound = flite_text_to_wave([self.cleanString UTF8String], voice);
[self performSelectorOnMainThread:#selector(backToForegroundTextToSpeech) withObject:nil waitUntilDone:YES];
[pool release];
}
#end
ok - so this isn't 100% thread-safe, but it is idiomatic.
the variant which is more resistant to threading issues would pass the string as an argument to backgroundTextToSpeech:(NSString *)text. backgroundTextToSpeech:(NSString *)text would then create a copy of the argument text (and of course release the copy before pool is destroyed).

You don’t understand how autoreleasing works. You assign the cleanString variable an autoreleased object and then start some processing on background that uses this value. But when the method that started the background processing returns the control to the main run loop, the autoreleased string stored in the cleanString gets deallocated and the background thread runs into a zombie.
You could simplify the code using Grand Central Dispatch:
- (void) startProcessing {
NSString *source = [NSString stringWithWhatever…];
dispatch_queue_t targetQ =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(targetQ, ^{
sound = flite_text_to_wave…;
dispatch_async(dispatch_get_main_queue(), ^{
[self speechProcessingDone];
});
});
}
The advantages are that you don’t have to maintain your own autorelease pool, you don’t have to add extra methods just to have one line executed in background and the block will retain your string, so that you won’t crash.
But you should certainly not use GCD just to get around the autoreleasing issue without knowing what’s happening. Memory management is the base, you must be 100% sure what you are doing.
If this is above your head, make sure you know how memory management works in Cocoa, for example by reading this Objective-C tutorial by Scott Stevenson. You have to do this, there is no way around.
Then come back to the code and you will see that the mutable strings that you store into cleanString are autoreleased, meaning they will get deallocated sometime soon after you leave the current function. After running the selector in background, you exit the current function and the string stored in cleanString gets deallocated. Soon after that the background thread gets to the following line:
sound = flite_text_to_wave([cleanString UTF8String], voice);
But as the object stored in cleanString was already deallocated, you get the crash. The solution is simply to retain the object until you are done with it. You can retain it before running the background thread and release when the background thread ends.

Related

Error in batch updating into core data with the thread

I am facing an issue in updating to core data using batch updating on a background thread. In the below code I am using main thread to notify user with a progress view and a string which calls a method in appdelegate. But if I am getting bad access error in the NSEntity line in random number of data where I have thousands of objects to update.if I uncomment the NSLOG i have indicated below there is no error, or if i comment the main thread no error, or if I dont update through batch instead if I use bulk update then also no error. IF i comment autorelease pool also the error is appearing. Will some body help me on this please.
Thanks in advance,
Cheers,
Shravan
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
NSUInteger iterator = 1;
for (int i = 0; i < totalNo; i++) {
NSDictionary *alertResult = [[alertResultList objectAtIndex:i] retain];
if (alertResult == nil) {
continue;
}
//managedObjectContext = [appDelegate.managedObjectContext retain];
NSLog(#"Object Count:%u", [[managedObjectContext insertedObjects]count]);
AlertResult *result = (AlertResult *)[NSEntityDescription
insertNewObjectForEntityForName:#"AlertResult"
inManagedObjectContext:managedObjectContext];
[result setUserName:#"A"];
iterator++;
//When count reaches max update count we are saving and draining the pool and resetting the pool
if (iterator == kUploadCount) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
[tempPool drain];
tempPool = [[NSAutoreleasePool alloc] init];
iterator = 0;
}
//Adding code to change the display string for the lock view to notify user
float count1 = (float)(counter/totalAlerts);
counter = counter + 1.0f;
NSString *dispStr = [NSString stringWithFormat:#"%f",count1];//[NSString stringWithFormat:#"Loading %d out of %d alerts",(i+1),totalAlerts];
NSString *dispMess = [NSString stringWithFormat:#"Alerts %d of %d",(i+1),totalNo];
[self performSelectorOnMainThread:#selector(changeLockScreenMessageWith:) withObject:[NSArray arrayWithObjects:dispStr,dispMess, nil] waitUntilDone:YES];
//NSLog(#"count"); /* If I uncomment this line code runs fine */
[alertResult release];
alertResult = nil;
}
//If count is inbetween the update limit we are updating and we are draining the pool
if (iterator != 0) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
//[tempPool drain];
}
//Out side the previous method
- (BOOL)update {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"%#", [error userInfo]);
return NO;
}
return YES;
}
The most likely cause of the kind of crash you're describing is using your managedObjectContext across threads. managedObjectContext is not thread-safe. You must create a new MOC for each thread. I assume managedObjectContext is an ivar; you should never access your ivars directly like this (except in init and dealloc). Always use an accessor to handle memory management for you.
The reason NSLog makes it crash is because NSLog dramatically changes the timing of this function, and you have a race condition.

NSMutableString append data error

I have AsyncSocket like this.
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSString *message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
[data getBytes:&tdata];
if (tdata > 5) {
if(bheader){
if(!charS){
if([message isEqualToString:#"S"]){
CMSG = message;
charS=YES;
}
}
else{
NSMutableString *tmp = [[NSMutableString alloc] initWithString:#""];
[tmp appendString:CMSG]; <<<<< This is code error at loop 2,
[tmp appendString:message]; the first loop success but second is fail
CMSG = tmp;
[tmp release];
}
}
else{
if (message){
cmessage = [[NSString alloc]initWithFormat:#"%#%#",cmessage,message] ;
}
else
NSLog(#"Error converting received data into UTF-8 String");
cdata++;
if(cdata==idata) {
msgComplete=YES;
}
}
if (msgComplete) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:cmessage forKey:kNotificationMessage];
[notificationCenter postNotificationName:kNotification object:self userInfo:userInfo];
cmessage=#"";
CMSG=#"";
msgComplete=NO;
bheader=YES;
cdata=0;
charS=NO;
[cmessage release];
}
}
[sock readDataToLength:1 withTimeout:-1 tag:0];
}
This code fail at second loop at [tmp appendString:CMSG]; Thread 1: Program received signal "SIGABRT"
How fix this error ?
Thanks gurus,
CMSG is assigned to tmp but tmp is released right afterwards. CMSG needs to be retained throughout the loop.
CMSG = tmp; //<< should be CMSG = [tmp retain];
[tmp release];
Now you have another potential problem. You are using a deprecated method here
[data getBytes:&tdata];
if (tdata > 5) {
getBytes: was deprecated because of a potential buffer overrun and the result of getBytes is not appropriate to use for if(tdata > 5). If you want to check the length of bytes get that that from the NSData object.
I see an error that may or may not be the ultimate problem here. In the final conditional statement, you have:
cmessage=#"";
...
[cmessage release];
I don't think you should be releasing that string. I don't actually know all of the ins and outs of NSString objects that are defined in that way (as opposed to stringWithFormat: or initWithString:), but I don't think that you have ownership of that string object. You shouldn't have to release it.
Remove that release message and see if it helps.

Memory leak in NSMutableArray

while(sqlite3_step(selectstmt) == SQLITE_ROW) {
NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
//Expenseentry* temp=[[[Expenseentry alloc]init]autorelease];
//Expenseentry* temp=[[Expenseentry alloc]init];
temp=nil;
temp=[[Expenseentry alloc]init];
//memory leak here
temp.ID=[NSString stringWithFormat:#"%d",primaryKey];
//memory leak here
int i=1;
#try{
//Expenseentry* temp=[[Expenseentry alloc]init];
//tried this but no luck
NSString *s=[[NSString alloc]initWithFormat:#"%f",sqlite3_column_double(selectstmt, 1)];
temp.amount=s;
[s release];
[arrreturn addObject:temp];
//[temp release];
//if i uncomment this app crashes
//[formatter release];
//printf("\n daata count %d ",[arrreturn count]);
}
#catch(id ex )
{
printf("ooooopssss exception ");
}
i++;
}
my expense entry class
#interface Expenseentry : NSObject {
NSString *ID;
NSString *amount;
}
#property (nonatomic, retain) NSString *ID;
#property (nonatomic, retain) NSString *amount;
#end
and .m is just
- (void)dealloc {
[ID release];
[amount release]
}
It looks like temp is an instance variable for that class
Make sure you release temp when you are done or right before you use it again
Try doing the following
[temp release];
temp=[[Expenseentry alloc]init];
temp.ID=[NSString stringWithFormat:#"%d",primaryKey];
The other option is to release after your done with it inside of the while(sqlite3_step) loop
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
...
temp=[[Expenseentry alloc]init];
temp.ID=[NSString stringWithFormat:#"%d",primaryKey];
... //Use temp
[temp release];
temp = nil; //Best practice to set it to nil
If the temp.ID string is leaking you need to look into the Expenseentry class to make sure your doing proper memory management there.
Edit: I now see the rest of your code posted
[arrreturn addObject:temp];
//[temp release];
//if i uncomment this app crashes
The reason why it is probably crashing is as I said before make sure you set it to nil after releasing
Edit 2: You are reusing the same object inside of the while loop also
You will want to move the temp allocation into the while loop or else every object in that array will point to the same object. I am not sure what you goal is with the code but take at a look at the following code.
while(i>5)
{
temp=[[Expenseentry alloc]init];
temp.ID=[NSString stringWithFormat:#"%d",primaryKey];
#try
{
NSString *s=[[NSString alloc]initWithFormat:#"%f",sqlite3_column_double(selectstmt, 1)];
temp.amount=s;
[s release];
[arrreturn addObject:temp];
}
#catch(id ex )
{
printf("ooooopssss exception ");
}
[temp release];
temp = nil;
i++;
}
the temp=nil seems a bit odd. whenever you assign the variable temp to a new object don't forget to release the previous object.
if you write:
Expenseentry* temp=[[Expenseentry alloc]init];
temp=nil;
you get a memory leak because you have created an Expenseentry object and then said good riddance to the object basically. You need to do a [temp release]; before assigning to nil on the iphone.
there could be other leaks like in your Expenseentry but you don't show how it looks like i.e. how the properties ID are declared.
Ok I found my mistake just posting if any one can explain this behavior:
memory leak cause array and array-object were not released.If I would release any of it app crashes.
mistake 1:[super dealloc] missing in expenseentry's dealloc.
doubt:why is it required to release super? when apple doc says you have to release object you own.
mistake 2:array being returned by this function is stored in instance variable(and synthesized property with retain as attribute) of caller.
and I have released that property in dealloc as it is retained.
receivedArr=fun()
in dealloc
[receivedArr release]

Adding large numbers of properties in Core Data, crashing when starting from phone but not from Xcode

I am trying to add data to CoreData. It works fine when I build from Xcode to the phone but when I try to start the app directly from iPhone it crashes on first save to the Context.
I read a text file that is synced via iTunes File Sharing, the file is pretty big (~350 000 lines). The values I get from the file is added to two different arrays (barcodes and productNames). The arrays are later batched through and the sent to the function where I save the data.
From the array loop:
[...]
words = [rawText componentsSeparatedByString:#";"];
int loopCounter = 0;
int loopLimit = 20000;
int n = 0;
int wordType;
NSEnumerator *word = [words objectEnumerator];
NSLog(#"Create arrays");
while(tmpWord = [word nextObject]) {
if ([tmpWord isEqualToString: #""] || [tmpWord isEqualToString: #"\r\n"]) {
// NSLog(#"%#*** NOTHING *** ",tmpWord);
}else {
n++;
wordType = n%2;
if (wordType == kBarcode) {
[barcodes addObject: tmpWord];
}else if (wordType == kProduct) {
[productNames addObject: tmpWord];
}
// Send to batch //
loopCounter ++;
if (loopCounter == loopLimit) {
loopCounter = 0;
NSLog(#"adding new batch");
[self addBatchOfData];
[barcodes release];
[productNames release];
barcodes = [[NSMutableArray arrayWithCapacity:20000] retain];
productNames = [[NSMutableArray arrayWithCapacity:20000] retain];
}
}
[...]
And then the save-function:
-(void)addBatchOfData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError *error;
NSUInteger loopLimit = 5000;
NSUInteger loopCounter = 0;
NSString *ean;
NSString *designation;
for (int i=0; i<[barcodes count];i++ ) {
ean = [barcodes objectAtIndex:i];
designation = [productNames objectAtIndex:i];
Product *product = (Product *)[NSEntityDescription insertNewObjectForEntityForName:#"Product" inManagedObjectContext:importContext];
[product setDesignation:designation];
[product setBarcode:ean];
loopCounter ++;
if (loopCounter == loopLimit) {
NSLog(#"Save CoreData");
[importContext save:&error];
[importContext reset];
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
loopCounter = 0;
}
}
// Save any remaining records
if (loopCounter != 0) {
[importContext save:&error];
[importContext reset];
}
[pool drain];
}
It's really irritating that it works fine when I build from Xcode. Hopefully there is a setting that I missed or something...
EDIT: Forgot to mention that I don't get passed the Default-screen and I don't have any logs. Can it have something to do with the provisioning?
Offload your file loading in a background thread and let the phone start up your main window and view. iOS will kill your app if you do not present a view in a timely manor (this is what you are seeing).
I have to do something like this for my xml -> CoreData converter code. I just present the user with a view notifying them of what is going on and a progress bar (I use https://github.com/matej/MBProgressHUD).
something like:
self.hud = [[MBProgressHUD alloc] initWithView:window];
// Set determinate mode
hud.mode = MBProgressHUDModeDeterminate;
hud.delegate = self;
hud.labelText = #"Converting Data File";
[self.window addSubview:hud];
// Show the HUD while the provided method executes in a new thread
[hud showWhileExecuting:#selector(convertToCoreDataStoreTask) onTarget:self withObject:nil animated:YES];
You just have to make sure that you use a separate NSManagedObjectContext in the new thread.
I would suggest that you implement this delegate method and then try to see what is going on with memory.
when running in the simulator, you have no memory constraints, but when running in the phone you do
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
}
I think I find the solution to my question.
What I was doing was that I started all the heavy data crunch in the "- (void) viewDidLoad {". When I changed it to start the crunch after I clicked a button in the app, it worked just fine.
Right now it's just finding out where the start the data crunch, any suggestions?

This loop chokes on 50,000 lines on the iPhone... can I improve it so it doesn't?

FILE *file = fopen([gpsFilePath UTF8String], "r");
char c[1024];
while(fgets(c, 1024, file)!=NULL)
{
NSString *cString = [[NSString alloc] initWithCString:c
encoding:NSMacOSRomanStringEncoding];
NSArray *split = [cString componentsSeparatedByString:#","];
if ([split count] != 3)
{
continue; //this should only happen on the first line
}
gpx = [gpx stringByAppendingString:[NSString stringWithFormat:#" <trkpt lat=\"%#\" lon=\"%#\"></trkpt>\n\n", [split objectAtIndex:0], [split objectAtIndex:1]]];
}
As others have pointed out, you are creating a lot of temporary objects. An awful lot. On top of that, the size of the temporary objects, at least gpx ones, is increasing with each pass of the loop. You might want to try something like:
NSMutableString *gpx = [NSMutableString string];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for(NSString *line in [[NSString stringWithContentsOfFile:gpsFilePath usedEncoding:NULL error:NULL] componentsSeparatedByString:#"\n"]) {
NSArray *split = [line componentsSeparatedByString:#","];
[gpx appendFormat:#" <trkpt lat=\"%#\" lon=\"%#\"></trkpt>\n\n", [split objectAtIndex:0], [split objectAtIndex:1]];
}
[pool release];
pool = NULL;
This example loads the contents of what's at gpsFilePath and splits it by new lines. Then, for each line, it splits the line on commas, and appends the results to the mutable string gpx. It wraps the processing part that creates lots of temporary objects in an autorelease pool so they get discarded as soon as possible.
At the end, the variable gpx will contain the processed results.
You're allocating several objects for each line of the file, and they aren't getting released because they're getting added to an autorelease pool, and the autorelease pool isn't getting a chance to drain. Add an autorelease pool that drains every some number of iterations:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
FILE *file = fopen([gpsFilePath UTF8String], "r");
char c[1024];
int line = 1;
while(fgets(c, 1024, file)!=NULL) {
NSString *cString = [[NSString alloc] initWithCString:c encoding:NSMacOSRomanStringEncoding];
NSArray *split = [cString componentsSeparatedByString:#","];
if ([split count] != 3) { continue; } //this should only happen on the first line
gpx = [gpx stringByAppendingString:[NSString stringWithFormat:#" <trkpt lat=\"%#\" lon=\"%#\"></trkpt>\n\n",
[split objectAtIndex:0],[split objectAtIndex:1]]];
if(line % 1000 == 0) // drain the pool every 1000 iterations
{
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
line++;
}
[pool release];
You are allocating cString without releasing or autoreleasing it. You should do a [cString release] each time when you're done with it.
Also, like the others said, you should use your own autorelease pool, and append to the existing gpx instead of creating a new string each time.
Can you use chunks bigger than 1024?