后台处理给了我BAD ACCESS崩溃?

时间:2011-02-02 08:36:40

标签: iphone objective-c xcode background-process

我在这里遵循本指南进行后台处理:

http://evilrockhopper.com/2010/01/iphone-development-keeping-the-ui-responsive-and-a-background-thread-pattern/

用于后台处理的唯一一行代码是:

sound = flite_text_to_wave([cleanString UTF8String],voice);

但由于某种原因,我得到了一个糟糕的访问信号。

调试它表明它也在该行崩溃。这是我现在在该部分中的代码。请记住,这大部分只是来自sfoster项目的默认Flite内容,之前没有任何问题,当它们全部在一起时,不会分成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];


}

任何想法我做错了什么/我可以做些什么来阻止这种情况发生?

编辑:使用下面发布的一些代码更改后调试器的图片:

enter image description here

2 个答案:

答案 0 :(得分:1)

您不明白自动释放的工作原理。您为cleanString变量分配一个自动释放的对象,然后在使用此值的背景上开始一些处理。但是当启动后台处理的方法将控件返回到主运行循环时,存储在cleanString中的自动释放字符串将被释放,后台线程将运行到僵尸中。

您可以使用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];
        });
    });
}

优点是您不必维护自己的自动释放池,您不必添加额外的方法只是为了在后台执行一行该块将保留您的字符串,这样你就不会崩溃。

但是你当然不应该使用GCD来解决自动释放问题而不知道发生了什么。内存管理是基础,你必须100%确定你在做什么。


如果这超出了你的想法,请确保你知道Cocoa中的内存管理是如何工作的,例如阅读Objective-C tutorial by Scott Stevenson。你来做这件事,没有办法解决。

然后回到代码中,您将看到存储到cleanString的可变字符串是自动释放的,这意味着它们将在您离开当前函数后很快被释放。在后台运行选择器后,退出当前函数,并释放cleanString中存储的字符串。之后不久,后台线程进入以下行:

sound = flite_text_to_wave([cleanString UTF8String], voice);

但是,由于存储在cleanString中的对象已经解除分配,因此崩溃了。解决方案就是保留对象,直到完成它为止。您可以在运行后台线程之前保留它,并在后台线程结束时释放它。

答案 1 :(得分:1)

跳出来的第一件事:

您的班级需要保留cleanString的保留计数。

通常,这是通过保留(或复制,通常最好使用NSStrings和其他具体/不可变类型)属性来完成的:

@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

好 - 所以这不是100%线程安全的,但它是惯用的。

对线程问题更具抵抗力的变体会将字符串作为参数传递给backgroundTextToSpeech:(NSString *)text。然后backgroundTextToSpeech:(NSString *)text将创建参数text的副本(当然,在pool被销毁之前发布副本。)