后台处理导致 BAD ACCESS 崩溃?

发布于 2024-10-15 18:45:57 字数 2359 浏览 4 评论 0原文

我在这里按照本指南进行后台处理:

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

并且唯一的1行进行后台处理的代码是:

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];


}

任何想法我做错了什么/我可以采取什么不同的措施来阻止这发生了?

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

在此处输入图像描述

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:

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浮萍、无处依 2024-10-22 18:45:57

首先跳出来的是:

您的类需要保存 cleanString 的保留计数。

通常,这是通过保留(或复制,这通常更适合 NSString 和其他具体/不可变类型)属性来完成:

@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 之前释放该副本)。

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).

雨巷深深 2024-10-22 18:45:57

你不明白自动释放是如何工作的。您为 cleanString 变量分配一个自动释放的对象,然后在使用该值的后台开始一些处理。但是,当启动后台处理的方法将控制权返回到主运行循环时,存储在 cleanString 中的自动释放字符串将被释放,并且后台线程会陷入僵尸状态。

您可以使用 Grand Central Dispatch< 来简化代码/a>:

- (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斯科特·史蒂文森的教程。你必须这样做,没有其他办法。

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

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

但由于存储在 cleanString 中的对象已经被释放,因此会发生崩溃。解决方案很简单,就是保留该对象,直到使用完毕为止。您可以在运行后台线程之前保留它,并在后台线程结束时释放它。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文