playSoundFileNamed 使声音文件保持打开状态,用完文件描述符并导致崩溃

playSoundFileNamed is leaving the sound files open, using up file descriptors and causing a crash

我有一个带有菜单场景和游戏场景的游戏。当我来回切换时,最终我崩溃了,

error = 24(打开的文件太多)

它总是在试图访问资源的线路上,例如,创建精灵,

spriteNodeWithImageNamed:

使用 I/O Activity 乐器我可以看到使用 playSoundFileNamed 创建的任何声音动作都在打开声音文件,而且它永远不会关闭。在我的游戏场景和菜单场景之间来回切换是构建这些文件的打开实例。 (注:我的声音动作都是属性)

我很确定我正在用完文件描述符。我想关闭这些文件以释放描述符。有谁知道这样做的方法吗?或者这听起来像一个错误?

感谢您的帮助!

  1. 首先我要告诉你我是如何防止文件描述符泄漏的(这是我实施的解决方案)。
  2. 那我就告诉你一个强制释放文件描述符的方法。

1。 我在所有测试中都注意到,当使用

打开声音文件时
self.soundAction = [SKAction playSoundFileNamed:@"Song1.mp3"]

playSoundFile:@"Song1.mp3" 的另一个调用不会再次打开文件,只要对第一个调用的引用仍然有效。解除分配场景和操作都不会导致文件关闭并且不会释放文件描述符。但是,这些 deallocs 确实会导致引用丢失。此时您已经刻录了一个文件描述符。

我在我的应用程序中看到的最糟糕的事情是 运行 的块。假设按下按钮执行此操作,

-(instancetype) init {
 .
 .
 .
self.playSound2 = [SKAction runBlock:(dispatch_block_t)^(){
    if (!weakSelf.condition1.hidden)
    {
        [weakSelf runAction:[SKAction playSoundFileNamed:@"Song2.mp3" waitForCompletion:NO]];
    }
}];
 .
 .
 .
}

每次按下按钮时,self.playSound2 都是 运行。所以 self.playSound2 会打开文件,然后在操作结束时,对 playSoundFileNamed: 的引用丢失了。每次按下按钮时,它都会燃烧一个文件描述符。我把代码改成了这样,

-(instancetype) init {
 .
 .
 .
self.sound2 = [SKAction playSoundFileNamed:@"Song2.mp3" waitForCompletion:NO];

self.playSound2 = [SKAction runBlock:(dispatch_block_t)^(){
    if (!weakSelf.condition1.hidden)
    {
        [weakSelf runAction:self.sound2];
    }
}];
 .
 .
 .
}

然后 self.sound2 保留引用,直到场景被释放,我只为每个场景演示刻录一个文件描述符。

所以...我的游戏有一个视图控制器,一个场景接一个场景地呈现。在应用程序终止之前,视图控制器不会被释放。所以我在 VC 中创建了对每个声音的引用。这意味着每个声音文件始终处于打开状态。所以以后对它的任何调用都不会再次打开文件,他们已经可以访问它,并且这些引用永远不会丢失。我通过在 VC 中为每个声音创建一个 属性,

ViewController.h

@property (nonatomic, strong)SKAction *sound001;
@property (nonatomic, strong)SKAction *sound002;
 .
 .
 .

ViewController.m

self.sound001 = [SKAction playSoundFileNamed:@"Bang.mp3" waitForCompletion:NO];
self.sound002 = [SKAction playSoundFileNamed:@"Boing.mp3" waitForCompletion:NO];
 .
 .
 .

这意味着我在任何给定时间使用了大约 55 个文件描述符(包括系统用完的一些)。其中 39 个是打开的声音文件。它们不再泄漏了。

2。 所以我想出了一个强制文件描述符关闭的解决方法,但我仍然觉得 ARC 应该处理这个问题。我修改了找到的代码片段 here

代码片段列出了所有使用的文件描述符。

这是我修改后的版本...

#import <sys/types.h>
#import <fcntl.h>
#import <errno.h>
#import <sys/param.h>

-(void)closeFileDescriptorsForSounds
{
    int flags;
    int fd;
    char buf[MAXPATHLEN+1] ;
    int n = 1 ;

    for (fd = 0; fd < (int) FD_SETSIZE; fd++) {
        errno = 0;
        flags = fcntl(fd, F_GETFD, 0);
        if (flags == -1 && errno) {
            if (errno != EBADF) {
                return ;
            }
            else
                continue;
        }
        fcntl(fd , F_GETPATH, buf ) ;
        NSLog( @"File Descriptor %d number %d in use for: %s",fd,n , buf ) ;
        ++n ;

// My modifications to the snippet...

        NSString *str = [NSString stringWithUTF8String:buf];
        NSString *theFileName = [str lastPathComponent];

        if ([theFileName isEqualToString:@"LoudBang.mp3"])
        {
            NSLog(@"FD is LoudBang");
            NSFileHandle *loudBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
        }
        else if ([theFileName isEqualToString:@"QuietBang.mp3"])
        {
            NSLog(@"FD is QuietBang");
            NSFileHandle *quietBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
        }
    }
}

我把buf转成字符串,然后把文件名隔离出来。然后我将文件名与我要关闭的已知文件进行比较。当找到我要关闭的文件时,代码使用以下内容在文件描述符周围创建一个 NSFileHandle,

NSFileHandle *loudBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];

这个 init 专门关闭了 NSFileHandle 的 dealloc 上的文件描述符(这几乎是立即的)。每次我按下相关按钮时,声音仍然会播放。文件描述符被释放并被其他文件使用。修改后的代码片段并不是要剪切并粘贴到某人的项目中,而是要显示强制释放文件描述符所采取的步骤。

希望这对您有所帮助。