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!
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];
}
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.
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.
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];
}
Related
Seems to be similar questions asked but none of the solutions seem to work for me. Cant for the life of me understand why my code doesnt work so here it is.
I dont get any errors but also get no sound.
#interface MainApp ()
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
-(void)playSoundFX:(NSString*) soundFile;
#end
#implementation MainApp
- (void)viewDidLoad {
[super viewDidLoad];
// Play sound
[self playSoundFX:#“sound.mp3"];
}
// Play sound
- (void)playSoundFX:(NSString*)soundFile {
// Setting up path to file url
NSBundle* bundle = [NSBundle mainBundle];
NSString* bundleDirectory = (NSString*)[bundle bundlePath];
NSURL *url = [NSURL fileURLWithPath:[bundleDirectory stringByAppendingPathComponent:soundFile]];
// Make an audioPlayer object and play file
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[audioPlayer setVolume:100.0f];
if (audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[audioPlayer play];
}
#end
*********vvvvvvvvv Amended Code that works vvvvvvvvv**********
#interface MainApp ()
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
-(void)playSoundFX:(NSString*) soundFile;
#end
#implementation MainApp
- (void)viewDidLoad {
[super viewDidLoad];
// Play sound
[self playSoundFX:#“sound.mp3"];
}
// Play sound
- (void)playSoundFX:(NSString*)soundFile {
// Setting up path to file url
NSBundle* bundle = [NSBundle mainBundle];
NSString* bundleDirectory = (NSString*)[bundle bundlePath];
NSURL *url = [NSURL fileURLWithPath:[bundleDirectory stringByAppendingPathComponent:soundFile]];
// Make an audioPlayer object and play file
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[self.audioPlayer setVolume:100.0f];
if (self.audioPlayer == nil)
NSLog(#"%#", [error description]);
else
[self.audioPlayer play];
}
#end
I am execute your code on my iPhone. It is work properly.Please Select a device and run your code. I hope it will execute properly with sound.
You got to segregate the functionalities..
Loading audio file in viewDidLoad and play it on play action, some thing like this
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#interface ViewController ()
{
AVAudioPlayer *_audioPlayer;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Construct URL to sound file
NSString *path = [NSString stringWithFormat:#"%#/sound.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *soundUrl = [NSURL fileURLWithPath:path];
// Create audio player object and initialize with URL to sound
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)playSoundTapped:(id)sender
{
// When button is tapped, play sound
[_audioPlayer play];
}
I'm trying to implement a fire-and-forget class method similar to
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
in the NSURLConnection, but I'm slightly confused about the memory management (I'm NOT using ARC at the moment).
My current code goes like this:
#interface StuffInfoDownloader() <UIAlertViewDelegate>
typedef void (^StuffInfoDownloaderCompletionBlock)(NSArray *stuffs);
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler;
#property (retain, nonatomic) StuffInfoDownloaderCompletionBlock completionHandler;
#property (retain, nonatomic) NSSet *identifiers;
#end
#implementation StuffInfoDownloader
#synthesize completionHandler = _completionHandler;
#synthesize identifiers = _identifiers;
+ (void)loadAsynchronouslyWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
StuffInfoDownloader *downloader = [[StuffInfoDownloader alloc] initStuffsWithIdentifiers:identifiers completionHandler:handler];
[downloader downloadStuffs];
[downloader release]; // will retain itself
}
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
if (!(self = [super init])) {
return nil;
}
[self retain];
_completionHandler = handler;
_identifiers = identifiers;
return self;
}
- (void)downloadStuffs
{
__block StuffInfoDownloader *me = self; // avoid reference cycle between self and the block
[StuffsConnection loadAsynchronouslyWithIdentifiers:self.identifiers completionHandler:
^(NSArray *stuffs, NSError *error) {
if(error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed."
message:#"TODO do localised string"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
[alert show];
[alert release];
} else {
me.completionHandler(stuffs);
[self release];
}
}];
}
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
#pragma unused(alertView, buttonIndex)
// try again
[self downloadStuffs];
}
- (void)dealloc
{
[_completionHandler release];
[_identifiers release];
[super dealloc];
}
Basically, I'm passing ownership of the object to itself, and releasing it in the handler. Any problems with that?
There are so many things wrong with this code. Besides the block property needing to be copy. You shouldn't do the [self retain]; and [self release]; (p.s. you missed a [self release] in the error case). That completely goes against the memory management rules. They are completely unnecessary if you do things right. Memory management in Cocoa is completely local -- a function or method needs only care what it does, not what any other code does. init has no reason to do [self retain], and does not have to "worry" about what any other code does. Period.
Then the _completionHandler = handler; _identifiers = identifiers; are wrong. The block needs to be copied if you are storing it in an instance variable; and the set needs to be retained or copied. You need to do either _completionHandler = [handler copy]; _identifiers = [identifiers retain]; or use the setter self.completionHandler = handler; self.identifiers = identifiers;.
Then, there is no issue of "retain cycle". A retain cycle requires a cycle -- A retains B, and B retains A. The block retains self, but does self retain the block? I don't see that anywhere. You are simply calling a class method of another class on this block. So you shouldn't do the weak reference. The weak reference is not correct anyway, since there is no guarantee that the current object will be valid by the time the block executes.
It seems that you (incorrectly) did the whole [self retain] thing, all in order to deal with the fact that you (also incorrectly) did not allow the block to retain self, as it should. Just get rid of this weak reference stuff, and get rid of the [self retain] stuff, and then it will not only follow the memory management rules, be more robust, but also look cleaner, simpler, and more understandable.
#property (nonatomic, copy) StuffInfoDownloaderCompletionBlock
completionHandler;
then in init:
self.completionHandler = handler;
You should never retain block if u haven't copied it before, that doesn't make sense .
By the way
if ((self = [super init])) {
/* initialization stuff*/
}
return self;
Seems that your code has lot of retainCycle flaws design
Not sure if this is a simulator issue or I am missing something, the following code gives me a memory leak although theAudio is released as in dealloc. I am playing a .wav file and the sound will change depending on the value of an integer. The App runs ok but 16 bit leak is flagged whwn I test this in xcode
Any suggestion is greatly appreciated:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#interface Water : UIViewController
<AVAudioPlayerDelegate>
{
}
#property (nonatomic,retain) AVAudioPlayer *theAudio;
-(IBAction) goButMenu: (id) sender;
-(IBAction) goDrinkMenu: (id) sender;
-(IBAction) playwater;
#end
#implementation Water
#synthesize theAudio;
-(IBAction) goDrinkMenu: (id) sender{
ButDrinkMenu *butdrinkmenu1 = [[ButDrinkMenu alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:butdrinkmenu1 animated:YES];
}
-(void) viewDidLoad {
if (BoyOrGirl == 1) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"Applause" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:#"bongo" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
}
-(IBAction) playwater{
if (BoyOrGirl == 1) {
NSString *path = [[NSBundle mainBundle] pathForResource:#"Applause" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
else {
NSString *path = [[NSBundle mainBundle] pathForResource:#"bongo" ofType:#"wav"];
theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
[theAudio play];
}
}
Definitely test on the device directly; testing for leaks with the simulator may return inaccurate results.
That said, there are several memory-related concerns with this code sample. You may wish to include your dealloc method here as that may offer some insight, but here's a problem (that appears in several of your allocations):
Your AVAudioPlayer isn't being assigned to your property, nor is it being released in scope. Ideally a player allocation would look more like:
AVAudioPlayer *myPlayer = [[AVAudioPlayer alloc] initWithContents....
[myPlayer setDelegate:self];
[self setTheAudio:myPlayer];
[myPlayer release];
As opposed to how you've implemented it, which assigns the player directly the instance variable:
theAudio = [[AVAudioPlayer alloc] initWith...
Because you assign the player directly to your instance variable, your player isn't being retained. This is likely to result in either premature release and a dangling pointer if you try and balance your calls properly, or it will result in leaking.
I am using custom delegate objects to do some cleanup tasks after a request finishes. ASIHTTPRequest doesn't retain delegates so I can't autorelease them. Right now this is how I am allocating and releasing the delegates.
App Delegate
MyDelegate *delegate = [[MyDelegate alloc] init];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:delegate];
MyDelegate.m
- (void)requestFinished:(ASIHTTPRequest *)request
{
[self release];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
[self release];
}
Is there a better way to do this? Having the delegates release themselves seems ugly and Xcode's build and analyze feels uncomfortable with what I'm doing.
A simple approach would be to maintain a mutable set of delgates for each active request in your main controller (the app delegate, in this case):
#interface MyAppController
{
NSMutableSet * activeDelegates;
}
#end
#implementation MyAppController
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
activeDelegates = [[NSMutableSet alloc] initWithCapacity:0];
return self;
}
- (void)dealloc
{
[activeDelegates release];
}
- (void)createRequest
{
MyDelegate *delegate = [[MyDelegate alloc] init];
[activeDelegates addObject:delegate];
[delegate release];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
...
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
MyDelegate *delegate = [request delegate];
[delegate doSomething];
[activeDelegates removeObject:delegate];
{
- (void)requestFailed:(ASIHTTPRequest *)request
{
[activeDelegates removeObject:[request delegate]];
}
#end
Why do you have a separate class purely to be a delegate? That's not how delegate objects typically work. Normally the controller that created the ASIHTTPRequest becomes the delegate, at which point you don't have to worry about releasing it because it will outlive the ASIHTTPRequest already (and if your controller gets dealloced before the ASIHTTPRequest is done, you need to cancel the request).
If You don't want to create a "controller" class for all your delegate instances, i would still at least follow the memory convention rules, and release the delegate immediately after setting it to asihhtprequest. Then i would include a propery in the delegate, something with a name managesItsOwnLifetime (BOOL) and on setting this to YES i would do a [self retain] ...
Dear community.
When i has a stable version of my application, while i don't start change code to multithread version. What was a difference between previous version:
in (void)applicationDidFinishLaunching:(NSNotification *)aNotification
i do loop to add to queue my code:
NSOperationQueue *opQueueImportUpdateFirstTimeData = [[[NSOperationQueue alloc]init]autorelease];
int i = 0;
for (NSString *carrier in currentCarriers)
{
AppController *operation = [[AppController alloc] initAndUpdateCarrier:carrier identifier:i];
[opQueueImportUpdateFirstTimeData addOperation:operation];
i++;
}
External class have:
- (id)initAndUpdateCarrier:(NSString *)forCarrier
identifier:(NSUInteger)iQuene;
{
[super init];
[self setIdentifierQuene:iQuene];
[self setStartForCarrier:forCarrier];
[self setPercentDone:0.0];
This point is a very important:
[self setDatabase:[[MySQLIXC alloc] init]];
u can't alloc other classes in process of u multithreading, i don't know why, but this is take malloc_error in whole queues
[self setAppDelegate:[[NSApplication sharedApplication] delegate]];
[self setManagedObjectContext:[[NSManagedObjectContext alloc] init]];
return self;
}
And in external class i have:
-(void) main;
{
[self makeUpdatesForCarrier:startForCarrier andTypeOfOperation:#"rates" forDirection:#"incoming"];// mySqlConnector:database];
it's a just some functions which working on local moc.
When i start application, interface didn't place it in background, and start visualization only after all queues will finish.
Before i try to alloc]init] MySQLIXC class inside my external class, but it gives me a lot of malloc_error_break exceptions, like somebody try to freeze memory for me.
Tnx.
Here is moc declaration:
in .h:
#property(retain) NSManagedObjectContext *managedObjectContext;
in .m:
#synthesize managedObjectContext;
Set persistent store coordinator:
[[self managedObjectContext] setUndoManager:nil];
[[self managedObjectContext] setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
Merge changes for with main moc:
- (void)mergeChanges:(NSNotification *)notification;
{
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
And in one place of my code i'm using a main moc (only for read information, i know that moc not thread safe):
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:#"DestinationsListForSale"
inManagedObjectContext:mainContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"lastUsedProfit > 0"];
[request setPredicate:predicate];
First off, CoreData is NOT thread safe. I would strongly advise that if you do not understand core data in a good deal of detail you keep you application singlethreaded or at the very least ensure that you always access the store from a single thread (probably best to use the main thread).
That said, this will not cause malloc_error_breaks AFAIK. You would see core data merge error exceptions and similar problems.
Could you show the code where you set up the moc further - just allocating and initing a moc is not enough - you have to set it's NSPersistentStoreCoordinator