Daniel Upton
Daniel Upton

Reputation: 5741

Why is this object release not OK, should i be releasing it?

You'll have to forgive me because i'm still fairly new to Obj-C but i'm quite confused..

I have this little sound board app with 12 buttons.. each calling the same IBAction..

When the user taps the button i'm calling alloc init on the player variable (which is declared in the interface part of the class)

This works all fine and dandy:

#pragma mark - IBActions

-(IBAction)userDidTapButton:(id)sender {
    [player stop];

    NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"clip" ofType:@"mp3"]];

    player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
    [player setNumberOfLoops:-1];
    [player play];
}

#pragma mark - Cleanup

- (void)dealloc {
    [player release];
    [super dealloc];
}

However this feels like when i'm calling alloc init repeatedly i'm leaving memory dangling (because i'm assigning the player pointer to a new variable without releasing the old one..)

To remedy this I tried adding this at the top of the IBAction:

-(IBAction)userDidTapButton:(id)sender {
    [player stop];
    [player release];

    ... etc ...

This works the first time i click the button (which seems strange to me as it's effectively a null pointer because it hasn't been allocated and initialised (right?)) but when i tap the button again it throws a EXC_BAD_ACCESS signal..

Why?

I allocated the memory should't I be freeing it too?

How am i supposed to free it?

Thanks in adavance!

Upvotes: 2

Views: 136

Answers (4)

Paul.s
Paul.s

Reputation: 38728

So I'll walk you through how I would do it and why.

In your .h file declare the player ivar with a property like this

// .h
@interface MyClass : UIViewController

@property (nonatomic, retain) AVAudioPlayer *audioPlayer;

// method signatures

@end

I named it audioPlayer just to be more explicit (this is personal preference).

In your implementation file you need to synthesize this ivar like this

// .m
@implementation MyClass

@synthesize audioPlayer = _audioPlayer;

// Do some stuff

@end

This will create the backing ivar and the getter and setter with the signatures - (void)setAudioPlayer:(AVAudioPlayer *)audioPlayer and - (AVAudioPlayer *)audioPlayer; but in the background they will be manipulating the ivar _audioPlayer.

You mentioned in a reply that you come from Ruby this can be likened to something like this attr_accessor :audio_player but in Objective-C it creates setters and getters than can deal with memory management depending on whether you pass in assign/retain/copy into the @property line.

This is how Apple does it in most of their examples and it means that it is clearer when you are accessing the ivar directly or going through a getter/setter.

I would now change your -(IBAction)userDidTapButton:(id)sender to look like this

-(IBAction)userDidTapButton:(id)sender 
{
  [self.audioPlayer stop];

  NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"clip" ofType:@"mp3"]];

  AVAudioPlayer *tmpPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];;
  self.audioPlayer = tmpPlayer;
  [tmpPlayer release]; tmpPlayer = nil;

  [self.audioPlayer setNumberOfLoops:-1];
  [self.audioPlayer play];
}

I have used the getters/setters anytime I have interacted with the audioPlayer ivar. This means that the memory management is taken care of each time I set the ivar (e.g. it releases the old player and retains the new). The reason this is using the getters/setters is because of the self.audioPlayer which will be compiled to the appropriate call like this:

self.audioPlayer;             // compiled to -> [self audioPlayer];
self.audioPlayer = tmpPlayer; // compiled to -> [self setAudioPlayer:tmpPlayer];

Now to tidy up and make the - (void)dealloc; method correct we should use the ivar directly without going through the getter/setters so I have to use the _audioPlayer ivar that we synthesized like this:

#pragma mark - Cleanup

- (void)dealloc 
{
  [_audioPlayer release];
  [super dealloc];
}

Upvotes: 3

JeremyP
JeremyP

Reputation: 86651

However this feels like when i'm calling alloc init repeatedly i'm leaving memory danglin

Yes, you are. You should release the old player before allocing the new one.

-(IBAction)userDidTapButton:(id)sender {
    [player stop];

    NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"clip" ofType:@"mp3"]];

    [player release]; // <<<=== this needs to be here

    player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
    [player setNumberOfLoops:-1];
    [player play];
}

However, it is better to create a property for the player to take care of all of this:

@interface MyClass : WhateverSuperClass
{
@private
    // other ivars

    AVAudioPlayer* player
}

@property (retain) AVAudioPlayer* player;

// other methods

@end

@implementation MyClass

@synthesize player;

// other stuff

-(IBAction)userDidTapButton:(id)sender {
    [[self player] stop];

    NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"clip" ofType:@"mp3"]];

    [self setPlayer: [[[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil] autorelease]];
    [[self player] setNumberOfLoops:-1];
    [[self player] play];
}

- (void)dealloc 
{
    [player release];
    [super dealloc];
}

Upvotes: 0

Nathan Day
Nathan Day

Reputation: 6037

You should probable release the previous player before you create a new one, or just reuse the one you have previously created. So after [player stop]; add [player release]; player = nil; the = nil; is so you can then safely send release in you dealloc methods. You should also probable add a [player stop]; before you [player release]; in you dealloc method. You may also want to keep a AVAudioPlayer instance for each button if there are not too many.

Upvotes: 0

ageektrapped
ageektrapped

Reputation: 14562

I sometimes get these weird problems too. A good habit to get into for Objective-C code is the same pattern with all allocated objects: call alloc init, do something (including retain it), then release. I find if you do that all in the same method things go predictably.

So, in your case, try the following:

-(IBAction)userDidTapButton:(id)sender {

    [myPlayer stop];
    [myPlayer release];
    NSURL *soundClip = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"clip" ofType:@"mp3"]];

    AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundClip error:nil];
    [player setNumberOfLoops:-1];
    [player play];
    myPlayer = [player retain];
    [player release];
}

where myPlayer is an instance variable in your class.

Upvotes: 0

Related Questions