zombamazomba
zombamazomba

Reputation: 1

Obj-C iPhone: Memory Management

iPhone beginner here, I am creating a simple music iPhone application called Piano Master, that produces a sound when a button is clicked.

Here is my code:

MusicViewController.h

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "PianoMasterAppDelegate.h"

@interface MusicViewController : UIViewController
<AVAudioPlayerDelegate> {}

- (IBAction)buttonClick:(id)sender;

@end

MusicViewController.m

#import "MusicViewController.h"

@implementation MusicViewController

- (IBAction)buttonClick:(id)sender
{
      NSString *path = [[NSBundle mainBundle] pathForResource:@"Piano1" ofType:@"wav"];
      AVAudioPlayer *theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL    fileURLWithPath:path error:NULL];
      theAudio.delegate = self;

      [theAudio play];
}

The sound plays when the button is clicked, but everytime the button is clicked, a new AVAudioPlayer is created, what are your best ideas on how I can manage this memory problem efficiently? I've thought of making an instance of AVAudioPlayer for the MusicViewController and using that, but there will still be a memory problem if I keep allocating a new AVAudioPlayer each time... Any help is useful, thanks.

Upvotes: 0

Views: 260

Answers (4)

sb.
sb.

Reputation: 1150

Assuming you'll want to play more than a couple different audio files, a dictionary of AVAudioPlayers would work well. For example, the first time a sound file is requested, create the AVAudioPlayer object and put it in your sounds dictionary, then for each subsequent request, just grab the existing AVAudioPlayer object from the dictionary.

Here's a simple implementation:

@interface MusicViewController : UIViewController <AVAudioPlayerDelegate> {
    NSMutableDictionary *sounds;
}
- (IBAction)buttonClick:(id)sender;
- (AVAudioPlayer *)playerForSoundFile:(NSString *)fileName;
@end


@implementation MusicViewController

- (id)init
{
    if (! (self = [super init]))
        return nil;

    sounds = [[NSMutableDictionary alloc] init];

    return self;
}

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

- (AVAudioPlayer *)playerForSoundFile:(NSString *)fileName
{

    AVAudioPlayer *player = [sounds objectForKey:fileName];

    if (! player) {
        NSString *path = [[NSBundle mainBundle] pathForResource:fileName 
                                                         ofType:@"wav"];
        NSURL *url = [NSURL fileURLWithPath:path];
        player = [[[AVAudioPlayer alloc] initWithContentsOfURL:url] autorelease];
        player.delegate = self;

        [sounds setObject:player forKey:fileName];
    }

    return player;
}

- (IBAction)buttonClick:(id)sender
{
    AVAudioPlayer *theAudio = [self playerForSoundFile:@"Piano1"];
    [theAudio play];
}

@end

Note that the AVAudioPlayer is "autoreleased", then added to the 'sounds' dictionary. This means that the when the dictionary is destroyed, all of the AVAudioPlayers will be as well. If this isn't clear, you should read up on memory management in Objective-C: http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/MemoryMgmt/MemoryMgmt.html

Upvotes: 0

Daniel T.
Daniel T.

Reputation: 33967

MusicViewController.h file

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "PianoMasterAppDelegate.h"

@interface MusicViewController : UIViewController <AVAudioPlayerDelegate> {
    AVAudioPlayer* _theAudio;
}

- (IBAction)buttonClick: (id)sender;

@end

MusicViewController.m file

#import "MusicViewController.h"

@implementation MusicViewController

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

- (AVAudioPlayer*)theAudio {
    if (_theAudio == nil) {
        NSString* path = [[NSBundle mainBundle] pathForResource: @"Piano1" ofType: @"wav"];
        _theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL: [NSURL fileURLWithPath: path error: nil];
        [_theAudio setDelegate: self];
    }
    return _theAudio;
}

- (IBAction)buttonClick: (id)sender {
    [self.theAudio play];
}

You might also want to release theAudio in the viewDidUnload method to free up memory, make sure you set the pointer to nil afterword like this:

- (void)viewDidUnload {
    [_theAudio release];
    _theAudio = nil;
}

Upvotes: 0

Joshua Lay
Joshua Lay

Reputation: 57

hotpaw2's answer is what i'd suggest doing.

But a key thing you're missing is memory management. You should add:

[theAudio play];
[theAudio release];

What you allocate in memory you should release. So even though you're creating an AVAudioPlayer each time. You'll be releasing the memory used. So you don't get any leaks.

Upvotes: 0

hotpaw2
hotpaw2

Reputation: 70673

Retain your audio players in another controller (MVC) object somewhere after initializing them. Then just reuse existing ones by calling the audio controller object from the view controller.

Upvotes: 1

Related Questions