Knowing when a view is going to be destroyed in UIView lifecycle - ios

I have a UIView which is part of a UIPageViewController.
I pass a strong object to this view, and am having problem with releasing.
In the past I think I used viewDidUnload to release any strong objects, and then Dealloc is called. Now deal is never called because of the strong objects.
What is the best way to know icm with a UIPageViewController that the object is not needed anymore. I.e. if it is the view beside the page the user is looking at, it might come back into view. So using viewWillDisappeart will not work I expect.
#interface DOTourFloorPlanViewController : UIViewController <UIScrollViewDelegate, DOAsyncImageViewDelegate> {
IBOutlet DOAsyncImageView* _imageView;
IBOutlet UIScrollView* _scrollView;
NSMutableArray* _beaconLabels;
UIView* _circle;
UIView* _centerDot;
DOTour* _tour;
CGRect _zoomRect;
int _circleCenterX;
int _circleCenterY;
bool _didZoomToLocation;
}
#property (strong, nonatomic) DOTour* tour;

Views aren't unloaded any more, newer devices don't have quite such tight memory restrictions and there are other optimisations. It seems that when you say view you're actually referring to the view controller, which is a little confusing.
If your view controller (VC) is provided with some data it can retain a reference to it until it's destroyed. The data should not be retaining the VC, or its view. If you have any kind of observation / delegation then that link should be weak to prevent retain cycles.
In this way the data will be released when the VC is no longer required, i.e. when it is removed from its parent or presenter.
Specifically for Core Data and references to NSManagedObjects you can help the system by calling refreshObject:mergeChanges: with a flag of NO to turn the object into a fault and remove its data from memory. You can do this when the view did disappear.

Related

iOS memory management with ARC

Let's say i'm working on an app with a large number of views and i have some problems understanding memory management when the UIViewController segues to another UIViewController.
Which of the following object should i release in viewDidDisappear: ?
#property (weak, nonatomic) IBOutlet UIImageView *background;
#property (strong,nonatomic) UILabel *playerLevel;
- (void)viewDidLoad
{
[super viewDidLoad];
map = [[MapView alloc]init];
[self.view addSubview:map];
}
Is this the correct way to do this ?
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:YES];
[_background removeFromSupeview];
[self.playerLevel removeFromSupeview];
[map removeFromSupeview];
_background = nil;
self.playerLevel = nil;
map = nil;
}
You don't need to do anything. ARC will implement the dealloc method for you, which will call all releases for your retained properties.
I really recommend you read the memory management documentation from apple, it will help to understand what ARC is really doing, including understanding how retain cycles can be avoided.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html
You don't need to release anything. ARC will take care of it when deallocating your view controller.
viewDidDisappear: only notifies the view controller that its view was removed from a view hierarchy. This is an optional method that your view can utilize to execute custom code when the view does indeed disappear. All views will be released automatically by ARC.
When you segue (presenting/pushing to a new scene), the previous view controller's views are generally not released. Only when a view controller is dismissed/popped (or you call an unwind segue) would it generally get deallocated. If you're seeing memory consumption continue to grow, make sure you are presenting/pushing to go forward, but dismiss/pop/unwind to return back to a previously presented view controller.
(A very long time ago, in low memory situations iOS would unload/release views that were not current visible, but Apple deprecated that in iOS 6.0 because it just didn't save much memory and caused too many problems for developers.)
Bottom line, because you're using ARC, you don't need this viewDidDisappear method at all. When the view is deallocated, that eliminates any strong references it maintains on its subviews, resulting in them being deallocated automatically, too (assuming you didn't create other strong references elsewhere, which you shouldn't be doing, anyway). Likewise, when the view controller is deallocated, any strong references it has are also resolved, resulting in those properties being released, too.
As an aside, a view controller maintains strong reference to the top level view, but it doesn't need to maintain strong references to that view's subviews. When addSubview is called, the top level view maintains its own strong references to its subviews. Thus the view controller owns the view, but the view owns its subviews.
This code sample suggest a bit of a logical inconsistency, where your IBOutlet is weak (as it should), but your label (and presumably the map) are strong. This isn't going to cause a problem, but suggests a logical inconsistency in the object ownership diagram.
I might suggest making the playerLevel property (and map, presumably) weak references (just like the IBOutlet). And, if instantiating these programmatically, you'd do something like:
#property (weak, nonatomic) UILabel *playerLevel;
- (void)viewDidLoad {
[super viewDidLoad];
UILabel *playerLevel = ...
[self.view addSubview:playerLevel];
self.playerLevel = playerLevel;
}
So, we create a local UILabel variable, configure it, add it to the subview, and then set the weak property to reference that label. The use of that local variable is important when dealing with weak properties, so it doesn't get released before you have a chance to call addSubview.

UITableView's delegate and dataSource references not set to nil. EXC_BAD_ACCESS

I have a class that handles paginated data for me. This class has the following properties:
#property (nonatomic, weak) UITableView *tableView;
#property (nonatomic, weak) id <DataSourceProtocol> dataSource;
I have a view controller that tells the pagination controller to load more data when needed. The pagination controller then tells its dataSource it needs new data, the dataSource downloads this data and lets the pagination controller know. The pagination controller saves this data and reloads its table view.
The problem comes when the view controller is deallocated during this process. For example, data is currently being retrieved by the pagination data source when the view controller is dismissed. Once the new data arrives and is passed to the pagination controller, the pagination controller tells its tableView to reloadData. Crash. Somehow at this point the tableView's delegate and dataSource (the view controller) are non-nil, despite being deallocated. The reference is now rubbish and the crash occurs.
I have seen some other questions and answers like this: Weak property not zeroing using ARC
But most deal with method calls that pass the weak reference as an argument, which mine does not.
Here is brief outline of the code (simplified and stripped down):
// In the view controller (also the pagination dataSource):
// Called by the pagination controller to get more data it needs
- (void)PCNeedsData:(PC *)pc
{
[_obj getData:^(NSArray *data) {
// By now the view controller is deallocated
[pc addData:data];
}];
}
// In the pagination controller
- (void)addData:(NSArray *)data
{
[_data addObjectsFromArray:data];
[_tableView reloadData]; // Crash because _tableView.delegate / dataSource are deallocated but non-nil
}
Obviously I can fix this issue by explicitly nilling out the tableView's delegate and dataSource in the view controller's dealloc. But I am mostly just curious why this step is needed. Do the blocks somehow cause the weak references to be retained (even though it is never referenced in the block / that makes no sense)?
Any help would be greatly appreciated.
Many of Apple's classes still use assign, rather than weak. You can check this yourself in the docs or the header file (command-click the delegate or dataSource property in Xcode). assign is the same as unsafe_unretained, which means if the object it's referencing is deallocated, it is not automatically set to nil.

Dangers of Unconventional UIViewController Usage

I'm going through some old code and trying to detect some hard to find bugs. I came across an unusual usage of a UIViewController where the controller is allocated, stored in a property, and its view is added as a subview instead of presenting the entire controller.
Let me start off by saying I know this is kind of hacky and abnormal. That said, what are the dangers in the following implementation? Are there any unexpected side effects that this could cause? What if MyOtherViewController unloads its view and recreates it due to receiving a memory warning at some point, could that cause any strange behavior?
#interface MyViewController()
#property (nonatomic, strong) MyOtherViewController *otherVC;
#end
#implementation MyViewController
- (void)viewDidLoad
{
self.otherVC = [[MyOtherViewController alloc] init];
[self.view addSubview:self.otherVC.view];
}
#end
What you are doing is creating custom view controller containers. This is not a bad thing, but you aren't doing it the way you're supposed to. There is a section in UIViewController's class reference that explains exactly how to accomplish what you're trying to do.
Take a look at Displaying a View Controllers Contents Programatically
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/UsingViewControllersinYourApplication/UsingViewControllersinYourApplication.html#//apple_ref/doc/uid/TP40007457-CH6-SW8
Note this: Important: Never install the view controller’s view into a view hierarchy directly.
I just tracked down a nasty BAD EXEC crash in a project I moved to (see nasty bug below).
I can say that using a UIViewController is very bad because:
You have to make sure the controller is not deallocated. The view won't because it is in view hierarchy with a superview, but the controller has no object to retain it. If it was added to the window as a rootController, to a tab controller, a navigation controller or presented by another controller (normal usage) it would be ok.
It won't receive orientation changes and messages that you would expect to get called besides viewDidLoad.
Nasty bugs. For instance in iOS 5 if this controller is not deallocated before you dismiss a modal controller you'll have a BAD EXEC crash that will drive you crazy. It seems the animation methods from the SDK expect your view controller to be present during the dismiss modal animation.

How to fix view retain cycle causing subview's dealloc to not be called?

I have a view controller which instantiates a bunch of UIButton subclasses and adds them to its self.view and also to a mutable array. These subclasses in turn have a retain property which points to another view. In most cases, the view property points back to the UIButton subclass' superview (the view controller's self.view to which they were added). But not always and not necessarily, which is why I am using this property and not the inherited superview one.
The problem I'm having is that when the view controller's dealloc does:
- (void)dealloc
{
[UIBUttonSubClassesArray release];
[super dealloc];
}
the UIButton subclasses' dealloc is not being called. So the additional release for the view property in these subclasses doesn't get called and, even when the view controller is dealloced, I'm leaking the view controller's view once for each of these UIButton subclasses.
But, if instead I make the subclasses' view property an assign, so that I need not call release in their dealloc, their dealloc does get called and even when there's no code referencing the view now, the app crashes.
Any ideas?
If your viewcontroller is not in the retain cycle, you can break the cycle in its dealloc (and in its viewDidUnload too) by setting your extra retain properties in your buttons to nil.
Although it is always preferable not to create retain cycles in the first place, I don't see enough clue in your question on how they should be avoided in your case.

Memory not released in ios view hierarchy

I have an iOS-App which uses ARC. I don't use InterfaceBuilder, all UI is generated manually. In that app I have several UIViewControllers with SubViewControllers. Those ViewControllers a tied together from a menu (-ViewController) who pushes them on the stack.
My problem is, that memory doesn't get freed when switching between the ViewControllers.
Is it wrong to keep references to the SubViewControllers like this?
#property (nonatomic, strong) UIViewController subViewController1;
#property (nonatomic, strong) UIViewController subViewController2;
viewDidUnload never gets called. Has anyone a good example how to build a clean view hierarchy?
By assigning the view controllers which get pushed on the stack to a strong instance variable / property, they will not be deallocated when popped off of the stack. The strong properties are holding on to the pushed view controllers even after they are popped off the stack, so they never get to a state where they can be deallocated.
Normally I do something like the following when pushing a next-level-down view controller onto the navigation stack:
SLSMoleculeSearchViewController *searchViewController = [[SLSMoleculeSearchViewController alloc] initWithStyle:UITableViewStylePlain];
[self.navigationController pushViewController:searchViewController animated:YES];
Under ARC, the new view controller will be allocated and will be retained on creation. When pushed onto the navigation stack, it will be retained once by the navigation controller. Because this new view controller is not referred to after being pushed, and is not assigned to a strong property or instance variable, ARC will release it after the second line.
Remember, it's still being retained by the navigation controller, so it's still live in memory. However, once the navigation controller pops it off the stack this view controller will be released. Since nothing is holding on to it at that point, it will be deallocated as expected.
If for some reason you need to maintain a reference to this sub view controller in your higher-level view controller, you could use a weak property or __weak instance variable. This will not hold on to the view controller, and will turn to nil once the controller is deallocated.
weak references are only supported for applications that target iOS 5.0, though, so you won't be able to do this for anything that needs to run on iOS 4.0. The 4.0-compatible unsafe_unretained property is not something I would recommend in this case, due to the danger of a pointer to deallocated memory.
This most likely has nothing to do with ARC. viewDidUnload is only called on a view controller when the view property is released / set to nil and this typically only happens if the app receives a memory warning.
Try triggering a memory warning in the simulator and see if that causes your viewDidUnload method to fire. If it does then everything is fine. If not, you are probably over-retaining your views somehow, perhaps by assigning them to other strongly retained properties.
There are exceptions to the view-retaining policy, for example the UINavigationController frees up views in its view controller stack if they aren't frontmost, but it does that by simply setting the view of its child controllers to nil when they're covered by another controller's view.
If you want your views to be released when they aren't onscreen, either set the controller's view property to nil in the viewDidDisappear: method, or stop retaining the view controllers when their views aren't onscreen and just create fresh controller instances each time you need to display them (that way both the controller and view will be released when not in use).

Resources