- First i'm going to tell you how I kept the file descriptors from leaking (this is the solution I implemented).
- Then i'm going to tell you a way to force the file descriptors to be released.
1.
I noticed in all my testing that when a sound file is opened using,
self.soundAction = [SKAction playSoundFileNamed:@"Song1.mp3"]
that another call of playSoundFile:@"Song1.mp3" does not open the file again as long as the reference to the first call is still valid. Neither deallocating the scene nor the action causes the file to be closed and does not release the file descriptor. However these deallocs do cause the reference to be lost. At which point you've burned a file descriptor.
The worst thing I saw in my app was a block that was run. Let's say pressing a button performs this action,
-(instancetype) init {
.
.
.
self.playSound2 = [SKAction runBlock:(dispatch_block_t)^(){
if (!weakSelf.condition1.hidden)
{
[weakSelf runAction:[SKAction playSoundFileNamed:@"Song2.mp3" waitForCompletion:NO]];
}
}];
.
.
.
}
Every time the button was pressed, self.playSound2 was run. So self.playSound2 would open the file and then at the end of the action the reference to playSoundFileNamed: was lost. Every time the button was pressed it burned a file descriptor. I changed the code to this,
-(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];
}
}];
.
.
.
}
Then self.sound2 holds the reference until the scene is deallocated and I only burn one file descriptor per scene presentation.
So... My game has one view controller which presents scene after scene. The view controller is not deallocated until the app terminates. So I created a reference in the VC to every sound. This means that every sound file is open all the time. So any future calls to it do not open the file again, they already have access to it, and these references are never lost. I do this by creating a property in the VC for each sound,
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];
.
.
.
This meant that I had about 55 file descriptors used at any given time (including some that the system uses up). 39 of these were the open sound files. They do not leak anymore.
2.
So I figured out a work around for forcing the file descriptors to close but I still feel like ARC should be taking care of this. I modified a code snippet found here
The code snippet lists all the file descriptors used.
This is my modified version...
#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];
}
}
}
I converted buf to a string and then isolated the file name. Then I compared the file name to a known file I want to close. When a file I want to close is found the code creates an NSFileHandle around the file descriptor using the following,
NSFileHandle *loudBangHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
This init specifically closes the file descriptor on dealloc of the NSFileHandle (which will be almost immediately). The sounds still play every time I press their associated buttons. The file descriptors are freed and used by other files. The modified code snippet isn't intended to be cut and paste into someone's project, rather to show the steps taken to force a file descriptor to be released.
Hope this helps.