【问题标题】:Recursive blocks == out of memory silent crash递归块 == 内存不足静默崩溃
【发布时间】:2012-06-15 23:32:53
【问题描述】:

我在带有 ARC 的 iOS5 中使用 Blocks 来通过 AVAudioPlayer 播放一个永无止境的录音循环。我在收到 AVAudioPlayerDidFinishRecording 消息后分配了一个新的 AVAudioPlayer,它开始了一个递归循环。我的应用程序在播放 120 个左右的音频片段后总是崩溃。我可以看到,随着每个音频文件的加载,它会占用越来越多的内存,但永远不会释放内存。

我正在使用单个 AVAudioPlayer 属性,因此我希望 ARC 每次分配新播放器时都会清理旧实例。

  1. 这些块是否会以某种方式导致旧的 AVAudioPlayers 继续存在?

  2. 使用观察者监控玩家并启动/分配新玩家会更好吗?

非常感谢您的帮助!

-(void)playNextAudioItem{
    //get ready to load audio doc from iCloud
    NSURL *ubiquitousContainer = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if(ubiquitousContainer){
        //query iCloud
        query = [[NSMetadataQuery alloc]init];
        [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
        NSPredicate *pred = [NSPredicate predicateWithFormat:@"(%K == %@)", NSMetadataItemFSNameKey, [NSString stringWithFormat:@"%@.caf", audioItemGUID]];
        [query setPredicate:pred];
        [[NSNotificationCenter defaultCenter] addObserverForName:NSMetadataQueryDidFinishGatheringNotification object:query queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif){
            //iCloud query finished gathering data
            [query disableUpdates];
            [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
            NSMetadataItem *item1 = [query resultAtIndex:0];
            audioCurrentVerse = [[audioDoc alloc] initWithFileURL:[item1 valueForAttribute:NSMetadataItemURLKey]];
            //open the audio file
            [audioCurrentVerse openWithCompletionHandler:^(BOOL success) {
                if(success){
                    //configure the session and play the audio file
                    AVAudioSession *audioSession = [AVAudioSession sharedInstance];        
                    [audioSession setActive:YES error:nil];
                    NSError *error = nil;
                    [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
                    audioPlayer = [[AVAudioPlayer alloc] initWithData:audioCurrentVerse.data error:&error];
                    audioPlayer.numberOfLoops = 0;
                    audioPlayer.delegate = self;
                    if([audioPlayer prepareToPlay]){
                        [audioPlayer play];
                    }
                }
            }];
            [query stopQuery];
            query = nil;
        }];
        [query startQuery];
    }
}

-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    //get an identifier for the next audio item
    audioItemIndex++;
    if(audioItemIndex >= rsAudioItems.recordCount){
        verseIndex = 0;
    }
    audioItemGUID = [rsAudioItems getRow:audioItemIndex andColumnNamed:@"audioItemGUID"];
    //recursive call to play the next audio item
    NSLog([NSString stringWithFormat:@"%d", playCount++]);
    [self playNextAudioItem];
}

【问题讨论】:

  • 如果您包含一些相关代码会更容易提供帮助。
  • 使用嵌套块的一般规则:不要。单独的内存管理是可怕的。请改用 while 循环。
  • 花时间创建一个SSCCE 可能会对您有所帮助;你可以自己解决这个问题。它肯定会让其他人更容易帮助你。
  • @JoshCaswell 感谢您的提示。下次发帖之前我会花时间做这件事。
  • 是的,你只是在旧块中调用一个新块,还是dispatch_async()-ing 到同一个队列?如果是前者,试试后者……

标签: objective-c ios ios5 automatic-ref-counting objective-c-blocks


【解决方案1】:

块在块被释放之前不会释放堆栈的快照。不要使用递归块(或者根本不使用递归,如果它在这种情况下没有真正意义的话),因为这些块永远不会被释放,所以快照会永远存在。

使用 for 循环可以缓解这个问题。您可以将它们添加到串行调度队列中,然后在执行后释放它们。

【讨论】:

  • 谢谢@JackLawrence!因此,如果我理解正确,我会在“while”循环内部添加一个串行调度队列,以便在播放每个音频文件时阻塞工作线程。 AvAudioPlayer 依赖于 avAudioPlayerDidFinishPlaying 回调。串行队列会在继续之前等待回调吗?
  • 我已经重写了我的代码以使用串行调度队列,但队列立即完成。我认为这是因为 iCloud MetaDataQuery 异步运行。
  • 我不确定我是否理解您在代码中所做的一切。这可能是打开 DTS(开发人员技术支持)问题并将您的项目发送给他们的好点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-12
  • 2020-11-22
  • 1970-01-01
相关资源
最近更新 更多