I have a memory leak in my iPhone app. I added Google AdMob to my app using sample code downloaded from Google. However, I was having a hard time getting it into testing mode so I added an extra variable as follows:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
I found the memory leak using Instruments. Instruments does not lead me to this line of code, it just dumps me in the main.m file. However, when I comment out the code relating to AdMob the leak goes away and I know enough to see that I haven't taken care of releasing this new variable. I just don't know exactly how I should set about releasing it. The variable r is not addressed in the header file so this is all the code that deals with it.
I tried adding:
- (void)dealloc {
[r release];
....
}
but that caused a build error saying "'r' undeclared". That's weird because it looks to me like I'm declaring r in the first quoted line above but I guess that's wrong. Any help would be much appreciated. I really have tried to educate myself about memory leaks but I still find them very confusing.
Just add [r release]; right below the code:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The variable r is declared only in that section of your code, so that's where it should be released. The point of releasing is to get rid of it as soon as you no longer need it, so the above should work perfectly for you.
If your r is locally declared (as it seems, judging from your snippet), then it cannot be accessed from outside its scope (here: the method it was declared in).
You either need to make it accessible within your class instance by declaring it an ivar.
Declaring it an ivar would look like this:
#interface YourClass : SuperClass {
GADRequest *request;
}
//...
#end
You then change your code to this:
request = [[GADRequest alloc] init];
request.testing = YES;
[bannerView_ loadRequest:request];
Also don't forget to release it in dealloc:
- (void)dealloc {
[request release];
//...
}
However this is not what you want in this situation (I've just included it to clarify why you get the warning about r not being declared).
You (most likely) won't need request any second time after your snippet has run, thus storing it in an ivar will only needlessly occupy RAM and add unwantd complexity to your class. Stuff you only need at the immediate time after its creation should be taken care of (released) accordingly, that is within the very same scope.
What you'll actually want to do is simply (auto)release it, properly taking care of it.
Keep in mind though that your loadRequest: will need to take care of retaining r for as long as it needs it. Apple's implementation does this, of course. But you might want to write a similar method on your own one day, so keep this in mind then.
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release]; //or: [r autorelease];
OP here. Thanks for all the detailed and thoughtful responses. This has definitely helped get a better handle on memory management. I did exactly as recommended, adding [r release]; right below the posted code. However, I still have a leak. I have isolated it to a single line of code. The following leaks:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The following does not leak:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
// [bannerView_ loadRequest:r];
[r release];
I figure I'm changing the retain count on bannerView with the loadRequest but I don't know how to fix it. I tried a [bannerView_ release] immediately after the [r release]; line (i.e. releasing locally) but that didn't work. I didn't expect it to because bannerView_ is declared elsewhere. I tried [bannerView_ release]; in the dealloc method but that didn't work. I also tried [bannerView_ autorelease]; locally. The wise heads at Google put [bannerView_ release]; in the ViewDidUnload method.
It's possible that Instruments is just messing with my head. The leak appears after about 10 seconds but the app performs well and the amount of memory leaked doesn't seem to be spirally upwards as the app continues to run. Is there such a thing as a benign memory leak?
Thanks again for your help,
Dessie.
Related
HarrisAnnotation *harrisAnnotation = [[HarrisAnnotation alloc] init];
[self.mapAnnotations insertObject:harrisAnnotation atIndex:0];
[harrisAnnotation release];
Running analyze on project show
Potential leak of an object
for
harrisAnnotation
Never mind, I'm not used to using Xcode. It was the line below.
I'm using the profiler in xcode 4 to determinate if I have any memory leaks. I didn't have this leak before, but with xcode 5 I have this one.
I'm trying to set an image for the tab item of my `UIViewController and the profiler marks this line :
image = [[UIImage alloc] initWithContentsOfFile:imgPath]; <<=== Leak : 9.1%
This is part of my code I don't understand why. What's the best way to resolve this issue?
NSString *imgPath;
UIImage *image;
IBNewsViewController *newsView = [[IBNewsViewController alloc] initWithURL:[tvLocal urlFlux] title:#"News" isEmission:NO];
[newsView setTitle:#"News"];
imgPath = [[NSBundle mainBundle] pathForResource:"news" ofType:#"png"];
image = [[UIImage alloc] initWithContentsOfFile:imgPath]; <<=== Leak : 9.1%
newsView.tabBarItem.image = image;
[image release];
image = nil;
UINavigationController* navNew = [[UINavigationController alloc] initWithRootViewController:newsView];
[newsView release];
newsView = nil;
EDIT:
No leak on iOS6.
Why it's leak on iOS7?
You should switch to the autoreleasing imageNamed: method. This has the added benefit of system level cacheing of the image.
NSString *imgPath;
UIImage *image;
IBNewsViewController *newsView = [[IBNewsViewController alloc] initWithURL:[tvLocal urlFlux] title:#"News" isEmission:NO];
[newsView setTitle:#"News"];
image = [UIImage imageNamed: #"news"];
newsView.tabBarItem.image = image;
UINavigationController* navNew = [[UINavigationController alloc] initWithRootViewController:newsView];
[newsView release];
newsView = nil;
To make life easier on yourself I'd switch your project to use ARC so you have less to worry about WRT memory management.
Replace this line
image = [[UIImage alloc] initWithContentsOfFile:imgPath];
With
image = [UIImage imageWithContentsOfFile:imgPath];
and check once.
First, switch to ARC. There is no single thing you can do on iOS that will more improve your code and remove whole classes of memory problems with a single move.
Beyond that, the code above does not appear to have a leak itself. That suggests that the actual mistake is elsewhere. There are several ways this could happen:
You're leaking the IBNewsViewController somewhere else
IBNewsViewController messes with its tabBarItem incorrectly and leaks that
You're leaking the UINavigationController somewhere else
You're retaining the tabBarItem.image somewhere else and failing to release it
Those are the most likely that I would hunt for. If you're directly accessing ivars, that can often cause these kinds of mistakes. You should use accessors everywhere except in init and dealloc. (This is true in ARC, but is absolutely critical without ARC.)
Leak detection is not perfect. There are all kinds of "abandoned" memory that may not appear to be a leak. I often recommend using Heapshot (now "Generation") analysis to see what other objects may be abandoned; that may give you a better insight into this leak.
Why differences in iOS 6 vs iOS 7? I suspect you have the same problem on iOS 6, but it doesn't look like a "leak", possibly because there is something caching the image that was removed in iOS 7. The cache pointer may make it look like it's not a leak to Instruments.
Speaking of which, do make sure to run the static analyzer. It can help you find problems.
And of course, switch to ARC.
For some reason, the allocation of two NSImageViews creates a huge memory leak.
Although dealloc method gets called in both NSImageViews.
I'm using ARC.
With NSImageViews
As soon as the following getter method is called, a lot of memory is used.
This would be ok, but it doesn't get deallocated when the window is being closed...
- (NSView *)animationView {
if (!_animationView) {
_animationView = [[NSView alloc] initWithFrame:self.bounds];
self.oldCachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.oldCachedImageView];
self.oldCachedImageView.wantsLayer = YES;
self.cachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.cachedImageView];
self.cachedImageView.wantsLayer = YES;
self.animationView.wantsLayer = YES;
}
return _animationView;
}
The first circle is when the getter method above is called.
The second is when the window is deallocated.
Without NSImageViews
Not with the two NSImageViews commented out.
- (NSView *)animationView {
if (!_animationView) {
_animationView = [[NSView alloc] initWithFrame:self.bounds];
/*
self.oldCachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.oldCachedImageView];
self.oldCachedImageView.wantsLayer = YES;
self.cachedImageView = [[NSImageView alloc] initWithFrame:self.bounds];
[_animationView addSubview:self.cachedImageView];
self.cachedImageView.wantsLayer = YES;
self.animationView.wantsLayer = YES;
*/
}
return _animationView;
}
No memory leak here...
Anyone an idea why this happens?
Use Instruments to trace the "Leaks". There is a Leaks tool in Instruments. Run it through the use of your app, all the way until you close your it completely. Make sure ARC isn't just deallocating it at a later time, which is possible. Keeping it in memory for a while allows for a faster retrieval if needed again.
However, if you do see a leak displayed, then there is an issue. Make sure there are no references to it you aren't noticing.
Use the leaks tool to figure out the memory location that is leaking if it displays some. Then try to match it with some object in your code. In other words, make sure it is the NSImageView that is leaking.
You can use:
NSLog(#"Location = %#",NSObjectHere);
To get the memory location. If it matches, then go back, once more, and find the reference you have to it. Unless it's an ARC bug, which is possible, though unlikely, then you are holding a reference to it somewhere.
For any more detail, please update your question with more code, no one can debug it for you without seeing more code.
Good luck!
EDIT
Tutorial explaining the use of the Leaks tool at http://mobileorchard.com/find-iphone-memory-leaks-a-leaks-tool-tutorial/
This is the code I'm using now, and it's not working (nothing happens when I press the button that calls this method). Previously, I had a property for audioPlayer and it worked (all the audioPlayers below were self.audioPlayer obviously). The problem was that when I tried to play the sound twice, it would end the first sound playing.
This is no good because I'm making a soundboard and want sounds to be able to overlap. I thought I could just make audioPlayer a local variable instead of a property and all would be ok but now the sound doesn't work at all and I can't figure out why. In all tutorials I've found for AVAudioPlayer, a property is made but no one explains why. If this can't work, what alternatives do I have to make sounds that can overlap?
- (void)loadSound:(NSString *)sound ofType:(NSString *)type withDelegate:(BOOL)delegate {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:sound
ofType:type]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
}
The reason you need a property or ivar is for the strong reference it provides. When using ARC any object without a strong pointer to it is fair game for deallocation, and in fact that is what you are seeing.
You are also correct that an AVAudioPlayer strong pointer will only allow one audio player to be referenced at a time.
The solution, if you choose to continue to use AVAudioPlayer is to use some sort of collection object to hold strong reference to all the player instances. You could use an NSMutableArray as shown here:
Edit I tweaked the code slightly so method that plays the sound takes an NSString soundName parameter.
#synthesize audioPlayers = _audioPlayers;
-(NSMutableArray *)audioPlayers{
if (!_audioPlayers){
_audioPlayers = [[NSMutableArray alloc] init];
}
return _audioPlayers;
}
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
[self.audioPlayers removeObject:player];
}
-(void)playSoundNamed:(NSString *)soundName{
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:soundName
ofType:#"wav"]];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (audioPlayer){
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
[audioPlayer play];
[self.audioPlayers addObject:audioPlayer];
}
}
Generally an AVAudioPlayer is overkill for an sound-effect/soundboard application. For quick sound "drops" you will likely find the audio toolbox framework, as outlined in my answer to this question.
From looking at the System Sound class reference, it seems like you
can only play one sound at a time.
It can only play one SystemSoundID at a time. So for example if you have soundOne and soundTwo. You can play soundOne while soundTwo is playing, but you cannot play more than one instance of either sound at a time.
What's the best way to be able to play sounds that can overlap while
still being efficient with the amount of code and memory?
Best is opinion.
If you need two instances of the same sound to play at the same time, then I would say the code posted in this answer would be the code to use. Due to the fact that each overlapping instance of the same sound requires creating a new resource, code like this with its audioPlayerDidFinishPlaying: is much more manageable(the memory can easily be reclaimed).
If overlapping instances of the same sound are not a deal-breaker then I think just using AudioServicesCreateSystemSoundID() to create one instance of each sound is more efficient.
I definitely would not try to manage the creation of and disposal of SystemSoundIDs with each press of a button. That would go wrong in a hurry. In that instance AVAudioPlayer is the clear winner on just maintainability alone.
I am assuming you are using ARC. The reason that the audio player doesn't work is because the AVAudioPlayer object is being released and then subsequently destroyed once the loadSound: method terminates. This is happening due to ARC's object management. Before ARC, the code:
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
if (delegate) audioPlayer.delegate = self;
[audioPlayer prepareToPlay];
[audioPlayer play];
would play the sound as expected. However, the AVAudioPlayer object would still exist long after the loadSound: method terminates. That means every time you play a sound, you would be leaking memory.
A little something about properties
Properties were introduced to reduce the amount of code the developer had to write and maintain. Before properties, a developer would have to hand write the setters and getters for each of their instance variables. That's a lot of redundant code. A fortunate side-effect of properties was that they took care of a lot of the memory management code needed to write setters/getters for object-based instance variables. This meant that a lot of developers started using properties exclusively, even for variables that didn't need to be public.
Since ARC handles all the memory management details for you, properties should only be used for their original purpose, cutting down on the amount of redundant code. Traditional iVars will be strongly referenced by default, which means a simple assignment such as:
title = [NSString stringWithFormat:#"hello"];
is essentially the same as the code:
self.title = [NSString stringWithFormat:#"hello"];.
OK, back to your question.
If you are going to be creating AVAudioPlayer instances in the loadSound: method, you'll need to keep a strong reference to each AVAudioPlayer instance or else ARC will destroy it. I suggest adding the newly created AVAudioPlayer objects into a NSMutableArray array. If you adopt the AVAudioPlayerDelegate protocol, you can implement the audioPlayerDidFinishPlaying:successfully: method. In which you can remove the AVAudioPlayer object from the array, letting ARC know that it's OK to destroy the object.
Here is a sample code, i am trying to import contacts for the iphone to my app.
-(IBAction)import_Clicked:(id)sender{
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init]; //leaking here
picker.peoplePickerDelegate = self;
// Display only a person's phone, email, and birthdate
NSArray *displayedItems = [NSArray arrayWithObjects:[NSNumber numberWithInt:kABPersonPhoneProperty],
[NSNumber numberWithInt:kABPersonEmailProperty],
[NSNumber numberWithInt:kABPersonBirthdayProperty], nil];
picker.displayedProperties = displayedItems;
[self presentModalViewController:picker animated:YES];
[picker release];}
i ran this on instruments and it shows me 100% leak at line where i alloc abpeoplepickernavigationcontroller. i realsed it after persentmodalviewcontroller. where else could i go wrong.
Any Help , Please.....
There seems to be a strange SDK bug at large here... Have a read of the official Apple dev forums here for more information and a solution.
Strange, that doesn't looks like a leak to me, heard that Instruments (rarely) report false leaks.
EDIT: forget what follows and read bbum comment instead :)
Could you please try removing [picker release] and then use autorelease instead :
BPeoplePickerNavigationController *picker = [[[ABPeoplePickerNavigationController alloc] init] autorelease];
Then see if instruments still report a leak? If not, keep your original code and ignore that false alert...
That is almost the same but using NSAutoReleasePool might change Instruments behavior.
Please also note that explicitly releasing like you did is a more cleaner approach than autoreleasing.