【问题标题】:Background processing gives me BAD ACCESS crash?后台处理让我 BAD ACCESS 崩溃?
【发布时间】:2011-02-02 18:33:28
【问题描述】:

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

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


}

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

编辑:修改后的调试器图片,下面发布了一些代码:

【问题讨论】:

    标签: iphone objective-c xcode background-process


    【解决方案1】:

    跳出来的第一件事:

    您的班级需要保留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 被销毁之前释放副本)。

    【讨论】:

    • 不,我把它贴进去了,但它又崩溃了。
    • 伙计,你不能只是从网上粘贴代码片段并希望结果有效:) 你必须不明白你在做什么。我会尝试扩展我的答案。
    • ok - 这意味着一个字符串被过度释放。我发布的示例看起来仍然不错 =) 三重检查您的实现:您还可以在哪里访问 cleanString?三重检查您是否复制了我正确发布的代码。您还可以测试字符串,以确保您正在寻找正确的位置。在self.cleanString = str; 添加[self.cleanString retain]; - 这将引入泄漏,但如果这是问题所在,您的程序(通常)不会以同样的方式崩溃。一旦发现问题,您显然需要消除引入的泄漏。
    • 最后,在某些情况下,多线程程序可能很难测试和调试。在使用多线程使程序复杂化之前,请确保您对内存管理有深刻的理解。使用泄漏工具确保您没有泄漏(释放不足的影响)。祝你好运
    • 好的,你必须通读苹果的内存管理指南并学习。几乎在每种情况下,您都需要使用属性来访问和设置实例变量。所以你所有的 objc 对象的类变量都应该转换为一个需要类似于我为 cleanString 所做的更改的实现。您发布的示例显示了多个其他变量以及其他问题,例如泄漏。 (一种变体)Leaks 是 Instruments.app 中的一个模板。每个 objc 开发人员都应该知道如何在 Cocoa 中管理内存。祝你好运
    【解决方案2】:

    您不了解自动发布的工作原理。您为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 中的对象已经被释放,你会遇到崩溃。解决方案是简单地保留对象,直到您完成它。可以在后台线程运行前保留,后台线程结束时释放。

    【讨论】:

    • 对不起,我不明白。正如我所说的那样,大部分代码都是从 flite 导入的,我所做的唯一发布就是使用池,为此我完全按照我发布的链接上的指南进行操作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-24
    • 1970-01-01
    • 1970-01-01
    • 2021-12-02
    • 2010-12-29
    • 2019-01-19
    相关资源
    最近更新 更多