I am working on a project on iPhone. I am now initiating a new UIViewController from another UIViewController, and then switch between them. Here is my code.
iGreenAppDelegate *delegate = [UIApplication sharedApplication].delegate;
if(checkInViewController) {
[checkInViewController release];
checkInViewController = nil;
}
checkInViewController = [[CheckInViewController alloc] initWithCheckpoint:checkpoint];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.8];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:[delegate window] cache:YES];
[[delegate rootTabBarController].view removeFromSuperview];
[[delegate window] addSubview:checkInViewController.view];
[UIView commitAnimations];
The Problem is the second time I initiate the UIViewController, I want to release it to avoid causing memory leak. The Debugger displays
iGreen(916,0x3f60348c) malloc: error for object 0x130350: incorrect checksum for freed object - object was probably modified after being freed.
set a breakpoint in malloc_error_break to debug
This is strange because similar codes in other parts don't return such error. Moreover, I tried autorelease, but the program will immediately crash and the Debugger says I am modifying finalized layers.
I've been working on the problem for a whole night, and still confused about it.
Set a breakpoint in malloc_error_break to debug.
Do that and post the backtrace.
Usually, this means that you corrupted memory, but it may also mean that you have an over-released object. Try Build and Analyze, too.
Apart from setting a breakpoint in malloc_error_break - press Command-6 in xCode to jump to the breakpoints tab - also enable the malloc aids in your scheme.
Go the the schemes selector, choose "Edit scheme" find the "Run" target and go to the "Diagnostics" tab. Below memory management enable scribble, guard edges, guard malloc and zombie objects.
With a bit of luck xCode will catch you writing outside your allocated memory and corrupting memory.
It's like adult supervision for dealing with memory...
Understand the error message: it's saying that something continued using (and modifying) the object after you freed it. This code frees it and does not modify it thereafter, but you have to ask what else could possibly continue using it (without knowing it was already freed).
Each time the code in this snippet runs, it releases (frees) any existing checkinViewController, and allocates a new one, and clearly it never touches the old one again. But who else may have a pointer to the old object?
Possibly other code you wrote, and possibly [delegate window], which gets a reference via "[[delegate window] addSubview:checkInViewController.view];"? Hopefully the latter takes its own reference, meaning release won't immediately free it.
But watch out for anywhere you're copying that pointer without adding a reference. If you do this somewhere, and then elsewhere (such as the above snippet) someone calls release on the same pointer, you may now have a pointer to an object that's been freed.
There are a couple of things going wrong design wise in your code. First your release the checkInViewController without removing its view from its superview (if any), then you remove the rootTabBarController's view from its superview, without doing anything to the controller itself, and you don't add the checkInViewController to the rootTabBarController or the rootViewController property of the window, so it's in the air (just retained by your current object). What happens when this (current) object gets deallocated but the view of the checkInViewController stays put (retained by) on the window?
If you release your checkInViewController but its view is still retained by the window, it is probably going to create some issues...
About the error, I think there's somewhere a weak reference (not retained) to your object that acts on it after it's freed.
Related
My app is crashing with the last message in the device console:
objc[5105]: Cannot form weak reference to instance (0x10801ec00) of class UIPageViewController. It is possible that this object was over-released, or is in the process of deallocation.
Randomly app also crashed with the same error message but UINavigationController instead of UIPageViewController. A bug is reproducible on the simulator and physical devices (iOS 11.2.5).
Profiling with "Zombie" template in Instruments did not give any valuable information. I found that outdated guide useful:
http://www.corbinstreehouse.com/blog/2007/10/instruments-on-leopard-how-to-debug-those-random-crashes-in-your-cocoa-app/
My answer might not fix your issue but I hope it will help to track it. Additionally, I'll appreciate if someone explains why ARC fired on a different thread.
By subclassing UIPageViewController and setting breakpoint in it's dealloc I found that it's not executed on the main thread.
don't do this in production - only play around when debugging
Synchronously removing its view from view hierarchy on the main thread did fix the issue. I have no view controller container responsible for that:
- (void)dealloc {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.view removeFromSuperview];
});
}
However it was not even about the view, while just delaying execution was enough to fix the crash, and the view will be cleaned together with its superview.
- (void)dealloc {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"%#", self);
});
}
View controllers were stored in the array, and I knew that when this array was destroyed, view controllers are as well. I checked on which thread object holding the only one reference to this array is destroyed, and it is main thread. ARC should release objects on same thread where the last reference to the object was removed, but that is not the case in my app. https://stackoverflow.com/a/32800112
How did I fix my issue:
I don't why it was so. I did not search for a reason anymore when I found that issue will also disappear when I manually remove reference to the array, right before removing the last reference to its owner.
I'm having a problem with a view controller that's dismissed and not referenced but still in memory, just wondering in general when is the object actually released in memory when no one references it?
The way I used to test is that I installed the PVC tool from Facebook and use it to print out the view hierarchy when the view controller is presented, after it's dismissed, I make sure no one's referencing it and paused the execution so I can po the memory address of the view controller from the previous PVC tool, but I can still see the view controller instance there.
Thanks!
You appear to be confusing being released and being cleared from memory. When the class is destroyed, the memory it occupied is not zeroed, just like when you delete a file in the filesystem, the disk blocks are not zeroed either.
This would simply take up too much time and have very little benefit.
Being released simply means the memory the class occupied can now be re-used.
One way to see if the class has been destroyed is to add a log in the dealloc method:
- (void)dealloc
{
NSLog(#"I'm being destroyed");
}
my code snippet:
- (void)viewDidUnload{
[super viewDidUnload];
self.statusView = nil;
self.tableView = nil;
self.noDataView = nil;
}
In a rare situation, my app crashed in line self.noDataView = nil;. When I debug by po self, it seemed that it's pointing something other than current controller. What is possible reason?
PS:self.tableView's delegate and dataSource is set to self in init method. Does that have any relation to this?
First, [super viewDidUnload] should be used as the last statement. However, that won't fix your error, probably.
The reason for your problem is quite simple. Your controller is overreleased somewhere. Do you have zombie detection enabled? The code where the application crashes is usually irrelevant because the problem happened earlier.
viewWillUnload is deprecated now, you can't count on it anymore, any question about it will lead you to the below references.
From Apple:
In iOS 6, the viewWillUnload and viewDidUnload methods of
UIViewController are now deprecated. If you were using these methods
to release data, use the didReceiveMemoryWarning method instead. You
can also use this method to release references to the view
controller’s view if it is not being used. You would need to test that
the view is not in a window before doing this.
And Quote WWDC 2012:
The method viewWillUnload and viewDidUnload. We're not going to call
them anymore. I mean, there's kind of a cost-benifit equation and
analysis that we went through. In the early days, there was a real
performance need for us to ensure that on memory warnings we unloaded
views. There was all kinds of graphics and backing stores and so forth
that would also get unloaded. We now unload those independently of the
view, so it isn't that big of a deal for us for those to be unloaded,
and there were so many bugs where there would be pointers into.
Edit:
For your problem in iOS 5.1, viewDidUnload is used to release anything you have made when the view was created, so unless you are creating or retaining objects it in viewDidLoad or your nib, you may not release them in viewDidUnload.
I had a hard to track bug which only appeared in the Release build of my app, but not in the Debug build. The relevant difference between the builds turned out to be that the Debug build was compiled without any compiler optimization, whereas the Release build was compiled with -O (the bug was reproducible on all other optimization settings as well). This is all on LLVM.
In my view controller I have a property self.basicInfoContainerView defined as:
#property (weak, nonatomic) IBOutlet UIView *basicInfoContainerView;
I then removed the subview from one view, and added it onto another.
[self.basicInfoContainerView removeFromSuperview];
[self.infoTextView addSubview:self.basicInfoContainerView];
Depending on the compiler optimization level, different things happened.
With optimization on: as soon as the view was removed from its superview, the view was deallocated and self.basicInfoContainerView was a zero'ed, and as a result was not added as a subview to the new view.
With optimization off: the subview was not immediately deallocated and was successfully added as a subview to the new view.
(When I changed the property storage qualifier to strong, the view survived in both cases, but even though that solved the problem, but that's not really my question.)
I would love someone to help me understand what is really going on here. Why does weak not immediately release my view (and zero the pointer if retain count == 0) when compiler optimization is turned off?
It's really not unusual to see non-optimized code that keeps extra local (and strong) references to objects. So your unoptimized code must have a local strong reference to this 'basicInfoContainerView'. That reference stays in scope through the method, and isn't being released probably until the method returns.
This is really just an accident of sorts that is masking a real bug in your code. The fact of the matter is as soon as you do [self.basicInfoContainerView removeFromSuperview], you can't expect that your basicInfoContainerView will survive, because you don't have any explicit strong reference to that view any more.
The way to fix this, of course, is to create an explicit strong reference to that view. Then you've made your intention clear to the compiler, and you should get the result you want whether the code is optimized or not:
UIView *containerView = self.basicInfoContainerView
// this local variable is that explicit strong reference you need
[containerView removeFromSuperview];
[self.infoTextView addSubview:containerView];
// the compiler can/will release this view when the local variable
// 'containerView' goes out of scope
Hope that helps.
When my iPhone app receives a memory warning the views of UIViewControllers that are not currently visible get unloaded. In one particular controller unloading the view and the outlets is rather fatal.
I'm looking for a way to prevent this view from being unloaded. I find this behavior rather stupid - I have a cache mechanism, so when a memory warning comes - I unload myself tons of data and I free enough memory, but I definitely need this view untouched.
I see UIViewController has a method unloadViewIfReloadable, which gets called when the memory warning comes. Does anybody know how to tell Cocoa Touch that my view is not reloadable?
Any other suggestions how to prevent my view from being unloaded on memory warning?
Thanks in advance
Apple docs about the view life cycle of a view controller says:
didReceiveMemoryWarning - The default
implementation releases the view only
if it determines that it is safe to do
so
Now ... I override the didReceiveMemoryWarning with an empty function which just calls NSLog to let me know a warning was received. However - the view gets unloaded anyway. Plus, on what criteria exactly is decided whether a view is safe to unload ... oh ! so many questions!
According to the docs, the default implementation of didReceiveMemoryWarning: releases the view if it is safe to do (ie: superview==nil).
To prevent the view from being released you could override didReceiveMemoryWarning: but in your implementation do not call [super didReceiveMemoryWarning]. That's where the view is released by default (if not visible).
The default didReceiveMemoryWarning releases the view by calling [viewcontroller setView:nil], so you could override that instead.
What appears to be working for me was to override setView: to ignore setting to nil. It's kludgy, but then, this is a kludgy issue, and this did the trick:
-(void)setView:(UIView*)view {
if(view != nil || self.okayToUnloadView) {
[super setView:view];
}
}
Could it be so simple?
Even though nowhere in the documentation this is mentioned, it seems that if I exclusively retain my view in viewDidLoad, then it does not get released on Memory Warning. I tried with several consecutive warnings in the simulator and all still seem good.
So ... the trick for the moment is "retain" in viewDidLoad, and a release in dealloc - this way the viewcontroller is "stuck" with the view until the time it needs to be released.
I'll test some more, and write about the results
I don't think any of these ideas work. I tried overriding [didReceiveMemoryWarning], and that worked for some phones, but found one phone unloaded the view BEFORE that method was even called (must have been in extremely low memory or something). Overriding [setView] produces loads of log warnings so I wouldn't risk that by Apple. Retaining the view will just leak that view - it'll prevent crashes but not really work - the view will replaced next time the controllers UI is loaded.
So really you've just got to plan on your views being unloaded any time they're off-screen, which is not ideal but there you go. The best patterns I've found to work with this are immediate commit so your UI is always up-to-date, or copy-edit-copy, where you copy your model to a temporary instance, populate your views and use immediate commit with that instance, then copy the changes back to your original model when the user hits 'save' or whatever.
Because the accepted solution has problems with viewDidUnload still getting called even though the view was blocked from being cleared, I'm using a different though still fragile approach. The system unloads the view using an unloadViewForced: message to the controller so I'm intercepting that to block the message. This prevents the confused call to viewDidUnload. Here's the code:
#interface UIViewController (Private)
- (void)unloadViewForced:(BOOL)forced;
#end
- (void)unloadViewForced:(BOOL)forced {
if (!_safeToUnloadView) {
return;
}
[super unloadViewForced:forced];
}
This has obvious problems since it's intercepting an undocumented message in UIViewController.
progrmr posted an answer above which recommends intercepting didReceiveMemoryWarning instead. Based on the stack traces I've seen, intercepting that should also work. I haven't tried that route though because I'm concerned there may be other memory cleanup which would also be blocked (such as causing it to not call child view controllers with the memory warning message).