I've read the other StackOverflow Questions and Answers, and understand this is a bug since iOS6 (or by design, having to deallocate the delegate, then view, who knows). I don't know why or how it hasn't been fixed.
Anywho, I've added the hot fixes from the other answers (below, for future readers):
- (void) viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self applyMapViewMemoryHotFixOnDisappear];
}
- (void)applyMapViewMemoryHotFixOnDisappear{
[self applyMapViewMemoryHotFix];
self.mapView.showsUserLocation = NO;
self.mapView.delegate = nil;
self.locationManager.delegate = nil;
[self.mapView removeFromSuperview];
self.mapView = nil;
}
- (void)applyMapViewMemoryHotFix{
switch (self.mapView.mapType) {
case MKMapTypeHybrid:
{
self.mapView.mapType = MKMapTypeStandard;
}
break;
case MKMapTypeStandard:
{
self.mapView.mapType = MKMapTypeHybrid;
}
break;
default:
break;
}
self.mapView.mapType = MKMapTypeStandard;
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
[self applyMapViewMemoryHotFix];
}
However, the question I have is, why does the memory not drop to before MapKit levels?
Is there something else I'm missing? Is this expected behaviour? There are no memory leaks judging by the profiler but obviously something isn't right...
Despite the use of the so beloved MemoryHotFix by the SO community regarding this problem, you should be sure that you're not holding any strong reference. As others have told, if you're using (read instantiating) views that hold a reference to the view controller they're in and that same view controller as a reference to that view you might get into a Strong Reference Cycle.
Such situation might block your deinit/dealloc methods rendering your cleanups unnecessary and useless.
As stated in the documentation:
You resolve strong reference cycles by defining some of the relationships between classes as weak or unowned references instead of as strong references.
So be sure to:
Check if your deinit(swift)/dealloc is actually being called (if not, it may indicate a Strong Reference Cycle).
Actually nil the delegates of the MKMapView and LocalitionManager as well as themselves.
Like this:
self.mapView.delegate = nil
self.mapView = nil
self.locationManager?.delegate = nil
self.locationManager = nil
Proof
Having that in mind, here is an example where there is one VC pushing another VC with a MKMapView, every vertical red line means "pushing new VC" and every green line means "popping it":
There's some initial setup (starting at 50 Mb +/-), but future push's don't cause any memory leak as shown. It is worth mentioning that this was captured using a real device. I've tested it using the simulator too and the results are coerent despite the initial setup being much higher (starting at 100 Mb).
Hope you guys can fix it and you might, as well, check your project for Strong Reference Cycles that might compromise your product in the future ;)
Related
Ran allocation profiler on my code. Found following problem on the inserted code. Could someone please point me what is wrong in the code. Added the picture so as too show the color coding.
Code for initializing the point is as below:
#autoreleasepool {
if(!coordString){
return nil;
}
if([coordString length]<3){
return nil;
}
__weak NSArray* coords=[coordString componentsSeparatedByString:#","];
if(nil != coords && ([coords count]==2)){
self = [super init];
if(nil != self){
self.coordX= [[coords objectAtIndex:0] doubleValue];
self.coordY = [[coords objectAtIndex:1] doubleValue];
return self;
}else {
return nil;
}
}else{
return nil;
}
}
Please suggest where the problem might be.
Allocation snapshot indicates the persistance memory.
There is no straight and clear solution one should expect on the memory related issues such as raised by my question above as there is no clear error is committed in the code. Leaks not picking anything specific. Hence #Avi above gave me a hint that the "Persistent" indicates the abandoned allocations. So his comment above gave me a direction that I need to look into the problems elsewhere.
Someone who hasn't watched, should spend time understanding handling of memory issues using Instrument on the link https://developer.apple.com/videos/play/wwdc2012-242/
More important is to create the "dealloc" function for you such classes and create deallocation code for your code and set the pointers to nil. In my case above I made sure to do that using following code:
-(void) dealloc{
if(nil != self.arrVertices && [self isKindOfClass:[SPCPlane2D class]]){
[self.arrVertices removeAllObjects];
}
self.arrVertices=nil;
}
With few hours of digging further I have got the instrument showing as below
So I'm relative new to objC programming. But not to C. In a more complicated app I think I have a memory leaks. I've programmed this just for make some tests. The app is very simple: it store in a MutableArray a series of integer that rappresent timers scheduled. The app has one NSTimer in the current runloop that check every second if it is the right time to ring comparing a counter with the right element of the MutableArray. Everything works, but memory in debug panel grow up, grow up, grow up…
I've try some variants but something still missing for me about ARC. I simply don't understand, since ARC is NOT a garbage collector, why memory grow and what I do wrong.
Here is the code:
-(id)initWithLabel:(UILabel *)label {
self = [super init];
self.list = [[mtAllarmList alloc]init];
self.label = label;
return self;
}
My class init function. I pass a label reference (weak beacause it is own by viewcontroller) to my class. I also allocate and init the class mtAllarmList that contain the MutableArray and other information (in the original app, file to play, volumes, eccetera).
-(void)ClockRun {
NSMethodSignature * signature = [mtClockController instanceMethodSignatureForSelector:#selector(check)];
NSInvocation * selector = [NSInvocation invocationWithMethodSignature: signature];
[selector setTarget:self];
[selector setSelector:#selector(check)];
[[NSRunLoop currentRunLoop] addTimer: self.time = [NSTimer scheduledTimerWithTimeInterval:1
invocation:selector
repeats:YES]
forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runUntilDate:[[NSDate alloc]initWithTimeIntervalSinceNow: 30]];
}
ClockRun: is the method the app call to start everything. It simply start the timer that fires every second to check:
-(void)check {
self.counter++;
int i = [self.list check:self.counter];
if(i == 1) {
[self writeAllarmToLabel:self.label isPlayingAllarmNumber:self.counter];
}
else if (i == 2) {
[self writeAllarmToLabel:self.label theString: #"Stop"];
[self.time invalidate];
self.counter = 0;
}
else {
[self writeAllarmToLabel:self.label theString:[[NSString alloc]initWithFormat:#"controllo, %d", self.counter]];
}
NSLog(#"controllo %d", self.counter);
}
Check: simply reacts to the return value of [list check: int] methods of mtAllarmList. It returns 1 if timer must ring, 0 if not, and 2 if the sequence ends. In that case self.counter will be set to 0 and the NSTimer will be invalidate.
-(id)init {
self = [super init];
self.arrayOfAllarms = [[NSMutableArray alloc]initWithCapacity:0];
int i;
for(i=1;i<=30;++i) {
[self.arrayOfAllarms addObject: [[NSNumber alloc]initWithInt:i*1]];
}
for(NSNumber * elemento in self.arrayOfAllarms)
NSLog(#"ho creato un array con elemento %d", [elemento intValue]);
return self;
}
In mtAllarmList init method simulates the costruction an array (I've try a variety of patterns) and log all the elements.
-(int)check:(int)second {
int maxValue = [[self.arrayOfAllarms lastObject] intValue];
if(maxValue == second){
self.index = 0;
return 2;
} else {
if ([[self.arrayOfAllarms objectAtIndex:self.index] intValue] == second) {
self.index++;
return 1;
} else {
return 0;
}
}
}
Check methods instead is very elementary and I don't think needs explanations.
So, why this simple very stupid app leaks?
Since you're doing this on the main run loop, you can (and should) simplify the ClockRun method:
- (void)ClockRun {
self.time = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(check) userInfo:nil repeats:YES];
}
That NSInvocation code was unnecessary and the NSRunLoop code could only introduce problems.
Having said that, this is unlikely to be the source of your memory consumption. And nothing else in the provided code snippets looks like an obvious memory problem. If you're 100% confident that the timer is getting invalidated, then the timer is not the problem. I wonder about the object graph between the view controller at this mtClockController. Or perhaps some circular reference in view controllers (e.g. pushing from A to B and to A again). It's hard to say on the basis of what's been provided thus far.
Sadly, there's not much else we can suggest other than the routine diagnostics. First, I'd run the the app through the static analyzer (by pressing shift+command+B in Xcode, or choosing "Profile" from the Xcode "Product" menu).
Second, you should run your app through Leaks and Allocations tools to identify the what precisely is leaking on each iteration. Do you have extra instances of the view controllers? Or just the mtClockController?
Until you identify what's not being deallocated, it's hard to remedy it. And Instruments is the best tool for identifying what's not getting released. In WWDC 2012 video iOS App Performance: Memory the demonstration sections of the video give pragmatic demonstrations of using Instruments (as well as a wealth of good background info on memory management).
Third, when I've got a situation where I'm not sure if things are getting deallocated when they should, I sometimes include dealloc methods that tell me when the object is deallocated, e.g.:
- (void)dealloc {
NSLog(#"%s", __PRETTY_FUNCTION__);
}
I'd suggest this not only for your key model objects, but your view controller, too. (Sometimes we agonize over our model objects only to realize that it's the view controller, itself, which is be retained by something else.)
Clearly Instruments is a much richer tool, but this can be used to quickly identify failure to deallocate (and show you what's maintaining the strong references).
I ran you app through Instruments, watching your custom objects, and everything is being deallocated properly. Below, I marked generation A, hit the button, let the timer expire, marked generation B, hit the button again, etc. I did that four times, and I then simulated a memory warning, and did one final generation. Everything looks fine (this is a compilation of six screen snapshots in one, showing the total allocations at each of the six generations):
I inspected your Generations, as well as the Allocations themselves, and none of your objects are in there. Everything is getting released fine. The only things there are internal Cocoa objects associated with UIKit and NSString. Cocoa Touch does all sorts of caching of stuff behind the scenes that we have no control over. The reason I did that final "simulator memory warning" was to give Cocoa a chance to purge what it can (and you'll see that despite what Generations reports, the total allocations fell back down a bit).
Bottom line, your code is fine, and there is nothing to worry about here. In the future, don't worry about incidentally stuff showing up in the generations, but rather focus on (a) your classes; and (b) anything sizable. But neither of those apply here.
In fact, if you restrict Instruments to only record information for your classes with the mt prefix (you do this by stopping a recording of Instruments and tap on the "i" button on the Allocations graph and configure the "Recorded Types"), you'll see the sort of graph/generations that you were expecting:
A couple of observations:
Instead of using the invocation form of scheduledTimerWithInterval, try using the selector form directly, in this case it's a lot simpler and clearer to read.
Since you're call runUntilDate directly, I don't think you're getting any autorelease pools created/drained, which would lead to memory leakage, specifically in the check function. Either don't call runUntilDate and allow the normal run loop processing to handle things (the normal preferred mechanism) or wrap check in an #autoreleasepool block.
Hello I have a memory issue in my iPad app. Each time I change from a view to another view (this transition is made with segues), the app is increasing the memory used and never releases the memory. It is always increasing the memory used.
Let's see an example:
I am in my first view "home" which has these lines in viewDidLoad and viewDidAppear
(void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]];
[self initializeHomeDataSources];
DateService* dateService = [[DateService alloc] init];
self.currentDate = [dateService today];
[self checkHomeStatus];
[self showEmptyHomeViews];
[self setUpFonts];
}
and this my view did appear method
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_homeAutomaticUpdate = YES;
//This is a Thread
[NSThread detachNewThreadSelector:#selector(automaticHome) toTarget:self withObject:nil];
[self.phrasesView startPhrasesThread];
if ([InternetService internetConnection]) {
[self synchronizeHome];
}
if (self.scheduleDataSource.currentEvent) {
[self loadMessagesFor:self.homeDataSource.currentEvent];
[self loadLibraryFor:self.homeDataSource.currentEvent];
} else {
[self loadLibrary];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_homeAutomaticUpdate = NO;
}
All the IBOutlet's are defined as (nonatomic, strong).
Each time the HomeView is loaded the memory increases it's quantity and I don't know what is happening.
Can anybody help me here? This problem is causing me consternation.
I'm guessing that you're going "backwards" to previous controllers using segues. Is that true? If so, that's your problem -- unless you use an unwind segue, you should never go backwards using segues because they always instantiate new controllers. So, when going back to previous controllers, either use an unwind, or use code that reverses your forward segues -- that is, popViewControllerAnimated: if the forward segue was a push, and dismissViewControllerAnimated:completion: if the segue was a modal.
Few questions:
Is your app killed after a while, because of memory usage?
Why you are creating new thread in -viewDidAppear?
Have you tried to simulate memory warning?
(In simulator: Hardware -> Simulate Memory Warning or Shift + CMD + M)
Does the memory gets down after memory warning or not?
This is not a whole answer for your question but your outlets must be weak unless their not top level objects.
All the IBOutlet's used should be (nonatomic, weak). Try this out..
I clear out the table view delegate and data source methods directly in dealloc as below:
- (void)dealloc
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
But looking at some online examples of dealloc, I see that everybody is checking whether the view is loaded before clearing out the delegate and data source like below:
- (void)dealloc
{
if ([self isViewLoaded])
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
}
Curious to know is it just to check if the memory is allocated to the view, if yes then clear else not. Or is there any specific reason for adding a check here?
If your controller is a table view controller then calling self.tableView when the view isn't loaded will cause it to load. If you're about to get deallocated then there is no point going to the effort of loading the view. So checking isViewLoaded is a cheap way of preventing that from happening.
What #Wain mentions is right. However, as per the iOS Memory Management Guidelines you should
NEVER use self to refer to an ivar inside init or dealloc precisely for situations like the one he described.
The correct way to do it would be:
- (void)dealloc
{
_tableView.delegate = nil;
_tableView.dataSource = nil;
}
Hope this helps!
I have a method that will dismiss the current view in the navigation controller and replace it with another view. The code looks like this
-(void)didTransferRequest:(NSString *)_transferComments {
AddRequestViewController *ar = [[AddRequestViewController alloc]
initAsTransferForRequestID:requestID
withClosingComments: _transferComments]];
UINavigationController *currentNav = self.navigationController;
[[self retain] autorelease];
[currentNav popViewControllerAnimated:NO];
[currentNav pushViewController:ar animated:NO];
[ar release];
}
[AddRequestViewController.m]
-(AddRequestViewController *)initAsTransferForRequestID:(int)requestID
withClosingComments:(NSString *)closingComments{
self = [self initWithStyle: UITableViewStyleGrouped];
if (self) {
_requestID = requestID;
_closingComments = closingComments;
}
return self;
}
The problem is that once the new view is pushed onto the nav stack, it crashes when the view attempts to access the contents passed in by _transferComments. The pointer is pointing to something else which would make sense since the view gets popped.
I was successful in using withTransferComments: [_transferComments copy] but the Analyzer identified a memory leak with this.
Is using copy safe and should I ignore the leak message or is there a better way to send the string over?
Your AddRequestViewController isn't taking ownership of _transferComments.
You need to read Cocoa Core Competencies - Memory Management and Basic Memory Management Rules.
In the code snippet you posted (without the copy), I deduce that AddRequestViewController doesn't send retain to _transferComments. If it wants to make sure _transferComments stays around, it needs to send it the retain message to take ownership of the string. When AddRequestViewController is done with the string, it needs to send it release to relinquish ownership. You would probably do this in -[AddRequestViewController dealloc].
Basically, your initAsTransferForRequestID:withClosingComments: method should look something like this:
- (id)initAsTransferForRequestID:(int)requestId withClosingComments:(NSString *)transferComments {
if (!(self = [super init]))
return nil;
_requestId = requestId;
_transferComments = [transferComments retain];
return self;
}
(Note that I'm using the common convention of naming instance variables with a leading underscore.) Your dealloc method should look like this:
- (void)dealloc {
[_transferComments release];
[super dealloc];
}
When you changed your code to copy _transferRequest, you did create a memory leak. The copy message creates an "owning" reference to the copy, and something needs to take responsibility for relinquishing that ownership. You didn't change either of your objects to do that.