Right place to unload strong refs to view if viewWillUnload is deprecated? - ios

I have a class to enable placeholders for UITextView. This class is called PlaceHolder, and it has initializer that accepts view. It then stores it in strong property and sets itself as view delegate. I store array of PlaceHolders in my strong array in viewDidLoaded:
self.placeHolders = #[[[PlaceHolder alloc]initWithControl:self.textView andPlaceHolder:#"text"]];
I then call
-(void)viewWillUnload {
for(PlaceHolder* holder in self.placeHolders) {
[holder unload]; // This method does self.view = nil; in each PlaceHolder
}
self.placeHolders = nil;
}
Nice. But viewWillUload is deprecated! It says I should use lowMemoryWarning, but it does not means view is unloaded!
So, what is the correct place to remove my placeholders?

If you have easily recoverable (i.e. cacheable) large data that you wish to unload when there is low memory, then implement didReceiveMemoryWarning and UIApplicationDidEnterBackgroundNotification to set that data to nil, thus releasing it. Accompany this by "lazy loading", so that if you fetch the data and the data is nil, you reconstruct it. In this way, you are always holding on to the data only if there is no memory pressure (or danger of memory pressure in the background).
You can automate this to some by extent using NSCache, though personally I've never had occasion to do that.

I finally understood everything, thanks to apple docs and #matt book.
Since iOS 6.0 system never unloads views automatically because new devices have much more RAM. But if I want, I can unload it myself from didReceiveMemoryWarning by nilling by view which then will be created automatically on next access to view due to laziness. But first I need to make sure view is not displayed now. So, correct way is to do the following:
-(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([self.view window] == nil) {
// Low memory and our view is not displayed.
// We can unload view (if we want), and when controller will be displayed,
// system will call self.view, and it will call loadView and then viewdidLoaded
self.view = nil; // Unload view manually
// Do any cleanup.
}
}

Related

Detecting if view still exists or active in completionHandler block

In my app I'm polling a web service for status updates, using a completionHandler block and making changes to the current view based on returned results when the callback executes.
- (void) tickTimer
{
[MyWebService myWebMethod:param1 completionHandler:^(NSString *result) {
// does view still exist?
[self myUpdateMethod];
// does property still exist?
self.theResult = result;
// does child view still exist?
_txtUpdate.text = result;
}];
}
But in the interim, it's possible that the view may have been unloaded as the user navigates elsewhere.
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to the background? I imagine it gets garbage collected at some point, but how do I tell if it's still safe to access by any of the references above, and what would happen if it's not?
If the view does still exist, how do I tell if it is also still the foreground view?
So, blocks create strong references to all objects pointers that are referred to in their closure. Due to this, your block is going to force [self] to stay in memory until the block is destroyed. If this isn't the behavior you want you should create a weak pointer to self and refer to it inside of the block:
__weak typeof(self) weakSelf = self;
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to
the background? I imagine it gets garbage collected at some point, but
how do I tell if it's still safe to access by any of the references
above, and what would happen if it's not?
If your view stays in the view hierarchy, it will stay in memory. Once there are no more references to the view it will be dealloced.
If you use a weak pointer like outlined above, then [weakSelf] will be nil if the view has been dealloced
If the view does still exist, how do I tell if it is also still the
foreground view?
I'm not sure what you mean by foreground view, but if you want to see if it's still in the view hierarchy then you can check the property -(UIView *)superview. If superview is nil, then it's not on the screen
If you use ARC right, it will not let you use deallocated viewcontroller.
You can use viewDidAppear and viewDidDisappear methods to know visible yours viewcontroller or not.

Questions about memory warnings

I have memory issues with my iOS app and I have several questions about that.
Firs of all, I'm working with iOS 6 and I'm using ARC.
Now let me explain my situation :
I have 2 views. From the first view, if I tap on a button, I create the second view (using alloc and init) and I display it as a modal using this code :
[self presentViewController:secondView animated:YES completion:^{
[secondView prepareToDraw]; // Function I use to start my computations and rendering
}];
At some time, when computations are finished I want to close my second view and go back to the first view. I' using this code from my second view :
[self dismissViewControllerAnimated:YES completion:^{
[self finished]; // Function I use to free some malloc
}];
I run my application with Instruments Allocations and Leaks and I have no Leaks.
Here is the code of my didReceiveMemoryWarning :
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
if ([self isViewLoaded] && ([[self view] window] == nil)) {
self.view = nil;
[self tearDownGL];
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
}
// Dispose of any resources that can be recreated.
NSLog(#"Resources freed");
}
The tearDownGL function frees OpenGLES resources like textures, vertex arrays, ...
When I run my application, after several switches between the first and second view I receive memory warnings and then my application crashes.
Here are my questions :
1- Is the application automatically freeing my UIImage, UIView, ... of my controllers? If not, how can I free them as I'm using ARC?
I also saw the viewDidUnload function but it's deprecated as the documentation says :
Called when the controller’s view is released from memory. (Deprecated
in iOS 6.0. Views are no longer purged under low-memory conditions and
so this method is never called.)
But if views are not purged anymore under low-memory conditions, how can I free more memory to prevent my application from crashing?
What should I do?
2- I put a breakpoint on the didReceiveMemoryWarning function for my 2 controllers. When I run the application on the simulator I simulate a memory warning.
I can see that the didReceiveMemoryWarning is called once for my 2 controllers.
But if I switch several times between my first and second controllers the didReceiveMemoryWarning is called once for my first view controller but is called several times for my second view controller. If I switched 3 times, the function will be called 3 times. So I guess, when I "close" my second view to go back to the first view, the second view is not freed and still exists. Why ? How can I force it to be destroyed ? (as I won't use it anymore and create a new one)
I create the second view controller in a function and I don't keep any reference to it (it is not stored in the class).
You should release (in ARC that means setting all strong references to nil) all memory (images, NSData objects, Arrays, all data represented by the model layer etc.) that are currently not required and can (easiyl) be re-created when they are used again. All of your other code should be written in a way that properties/iVars are checked for nil if those objects could have been released during a memory warning and then will be re-created.
I doubt that self.view is amongst that objects that might be disposal.
You may have displayed an UIImageView. That was probalby created with an UIImage object. You not not really need that UIImage in memory while the UIImageView is displayed. (If UIImageView still need it then it retains it or keeps a strong reference on its onw so that you don't have to worry about keeping the image itself.) THAT are the resources to be released.
If self.context is amongst the disposal resources, I cannot say. It may well be.
ARC does not always mean that the images, views ,etc gets released instantly. It gets added to the nearest arc pool and gets released.If the application may require it or uses it somewhere it gets added to the main pool which gets released only when the application terminates. So it is better to remove the object yourself if you think it has served it purpose. Especially in case of images it remains in the memory as it does not know whether it is getting used anywhere else or not.
Whenever you work with blocks, you should use a weak reference to self, since this can lead to retain cycles. So change your code to this:
__weak typeof(self) blockSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[blockSelf finished]; // Function I use to free some malloc
}];
Also, your code to free anything should be in dealloc. You don't need a custom method for it, if it only happens at the end of the lifetime of that controller.
Your first call also seems wrong:
[self presentViewController:secondView animated:YES completion:^{
[secondView prepareToDraw]; // Function I use to start my computations and rendering
}];
If prepareToDraw only happens once, when the controller is presented the first time, than you should run this code in viewDidLoad. This would also benefit your architecture, since only the controller itself should know what it has to setup in the beginning and to tearDown at the end.
Hope that helps. Perhaps you have other/more problems in your code.
Please have a look at auto-relase pools : AutoReleasePools
It reads :
Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint
Many programs create temporary objects that are autoreleased. These objects add to the program’s memory footprint until the end of the block. In many situations, allowing temporary objects to accumulate until the end of the current event-loop iteration does not result in excessive overhead; in some situations, however, you may create a large number of temporary objects that add substantially to memory footprint and that you want to dispose of more quickly. In these latter cases, you can create your own autorelease pool block. At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint
I had a similar issue, where I encapsulated all the big objets that I wanted to get rid of in an #autoreleasepool block.

Method for release resources in iOS 6.0 [duplicate]

So with viewDidUnload deprecated as of iOS 6, what do I need to do now?
Delete it, and migrate all of it's contents in didReceiveMemoryWarning, or leave it, and don't do anything in didReceiveMemoryWarning?
The short answer is that, in many cases, you don't need to change anything. And, you most certainly do not want to simply migrate all of the contents of viewDidUnload to didReceiveMemoryWarning.
Generally, most of us do the setting of IBOutlet references to nil in viewDidUnload (largely because Interface Builder would put that there for us) and do the general freeing of memory (e.g. clearing of caches, releasing of easily recreated model data, etc.) in didReceiveMemoryWarning. If that's the way you do it, then you probably don't require any code changes.
According to the iOS 6 viewDidUnload documentation:
Views are no longer purged under low-memory conditions and so this method is never called.
Therefore, you do not want to move the setting of your IBOutlet references to nil anywhere, because the views are no longer purged. It would make no sense to set them to nil in didReceiveMemoryWarning or anything like that.
But, if you were responding to low memory events by releasing easily-recreated model objects, emptying caches, etc., in viewDidUnload, then that stuff should definitely move to didReceiveMemoryWarning. But then, again, most of us already had it there already.
Finally, if you free anything in didReceiveMemoryWarning, just make sure your code doesn't rely upon them being recreated in viewDidLoad again when you pop back, because that will not be called (since the view, itself, was never unloaded).
As applefreak says, it depends upon what you were doing in viewDidUnload. If you update your question with explicit examples of what you had in your viewDidUnload, we can probably provide less abstract counsel.
The short answer:
Never use -didReceiveMemoryWarning for a balanced tear down as it might get called never or multiple times. If you have your setup in -viewDidLoad, place your cleanup code in -dealloc.
The long answer:
It's not easy to give a general answer, as it really depends on the situation. However, there are two important facts to state:
1. -viewDidUnload is deprecated and in fact never called starting with iOS6 and later. So, if you have your cleanup code in there, your app leaks under these OS versions
2. -didReceiveMemoryWarning might be called multiple times or never. So it's a really bad place for a balanced teardown of objects you created somewhere else
My answer is looking at the common case where you are using properties, for example:
#property (strong) UIView *myCustomView // <-- this is what I'm talking about
#property (assign) id *myDelegate
Here you have to do some cleanup, because you either created and own the customView or InterfaceBuilder created it, but you are retaining it. Before iOS 6 you would probably have done something like this:
- (void)viewDidLoad {
self.myCustomView = [[UIView alloc] initWithFrame:…];
}
- (void)viewDidUnload { // <-- deprecated!
[myCustomView removeFromSuperView];
self.myCustomView = nil;
}
...because (again) myCustomView is a retained property, created and owned by you and so you have to take care and "release" it (set it to nil) at the end.
With iOS 6, the best place to replace -viewDidUnload and to set the retained property to nil is probably -dealloc. There's also viewWillAppear and viewDidDisappear, but these are not tied to the lifecycle of your view/controller, but the display cycle (On the other hand, the -...appear methods are perfect for un-/registering notification listeners!). So it might not be appropriate to create and destroy views before and after every display. dealloc is the only method we can be sure of to be called at the very end of the controller's lifecycle. Note that you must not call [super dealloc] if you're using ARC:
- (void)dealloc {
self.myCustomView = nil;
}
However, if you're using viewDidLoad to do some view related setup that can be freed upon low memory conditions, the other posts showing how to deal with low memory situations are totally valid. In this case you can also use dealloc, but you have to check if your views are still there.
The Lifecycle of a ViewController
Maybe it's also helpful to look a the general lifecycle of a ViewController:
This is the lifetime of a viewController (lines in italic mean that these methods might be called multiple times):
init: ViewController loaded, no interface element (IBOutlet) available yet (all nil)
viewDidLoad: the nib/storyboard has been loaded and all objects are available. The user sees nothing yet
viewWillAppear: the view is about to be displayed
viewDidAppear: the view is on screen
viewWillDisappear: the view is about to go away
viewDidDisappear: the view just was taken off the window
viewDidUnload: NEVER CALLED in iOS6/7
didReceiveMemoryWarning: You don’t know if, when and how often this is called. Prior to iOS6 it might unload the view, after iOS6 it just purges an offscreen cache or does nothing
dealloc: the viewController is about to get destroyed
So, to sum it up there are various possibilities; what goes where now really depends on what was initialized where:
-dealloc if created in -init: or -viewDidLoad:
-viewWill/DidDisappear (paired with -viewWill/DidAppear)
-didReceiveMemoryWarning (might or might not be called)
If you need to know if your UIViewController is dismissing you can add this code to your viewWillDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if ([self isBeingDismissed] || [self isMovingFromParentViewController])
{
// Do your viewDidUnload stuff
}
}
It is not called at the same time as viewDidUnload did once in the view controller life cycle, but works in most cases for your needs!
Depends on what you do in viewDidUnload but you can use didReceiveMemoryWarning or dealloc to release data. See this.
In most typical cases, this method can be used in place of the old viewDidUnload.
// The completion handler, if provided, will be invoked after the dismissed controller's viewDidDisappear: callback is invoked.
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion NS_AVAILABLE_IOS(5_0);
Swift 2017 syntax:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if (yourChildViewController != nil) {
print("good thing we did this!")
}
yourChildViewControllerProperty = nil
super.dismiss(animated: flag, completion: completion)
}

What works in viewDidUnload should be moved to didReceiveMemoryWarning?

In new iOS 6, viewDidUnload is deprecated and we have been instructed to use didReceiveMemoryWarning instead, to manage objects in UIViewController instances and subclasses. Is it equally effective to assign nils to UIView kinds inside didReceiveMemoryWarning like the way it has been done inside viewDidUnload?
I am asking this because these two methods seems to be working differently. It seems like didReceiveMemoryWarning doesn't guarantee viewDidLoad to be called again to re-instantiate any necessary UIViews.
I suspect with iOS 6, memory management is done without requiring to manually deallocate UIView. Please help me to know what I have missed in understanding the lifecycle of UIViewController.
My preferred method is now the following:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if (self.isViewLoaded && !self.view.window) {
self.view = nil;
}
// Do additional cleanup if necessary
}
Note that the test self.isViewLoaded is essential, as otherwise accessing the view causes it to load - even the WWDC videos tend to miss that.
If your other references to subviews are weak references, you don't have to nil them out here, otherwise you want to set them to nil, too.
You should get rid of viewDidUnload completely, and every code there should move to appropriate places. It wasn't guaranteed to be called prior to iOS 6 before anyway.
In the iOS reference for viewDidUnload:, it states that this is deprecated for iOS 6 because
Views are no longer purged under low-memory conditions and so this
method is never called
It doesn't say anything about placing this code in didReceiveMemoryWarning:. Since views are no longer purged under low memory conditions, you never have to worry about cleaning up your views in either method.
The answer by Eiko is not correct, and we should NOT set self.view to nil when receiving low memory warning. Doing so is useless and may be harmful.
iOS 6 will automatically freeing bitmaps of views which is not currently displayed, See http://thejoeconwayblog.wordpress.com/2012/10/04/view-controller-lifecycle-in-ios-6/ for details.

Setting delegate to nil in iOS 6

Prior to iOS 6, we were supposed to
- (void)viewDidUnload {
self.someDelegate = nil;
[super viewDidUnload];
}
Now that viewDidUnload is deprecated, where do we set our delegates to nil? Thanks!
Views are no longer unloaded when receiving a memory warning in iOS6. So the view is never unloaded on such cases and viewDidUnload is never called in iOS6.
If you really want to mimic the old behavior (unloading the view upon receiving a memory warning) you now have to implement this behavior in the didReceiveMemoryWarning method of your view controller, after testing that the self.view.window property is nil (meaning the view is not on screen anymore, thus will be "unloaded", meaning that you are in the same situation as the old viewDidUnload case).
-(void)didReceiveMemoryWarning
{
if (self.isViewLoaded && !self.view.window)
{
// If view already loaded but not displayed on screen at this time (not attached to any window) then unload it
self.view = nil;
// Then do here what you used to do in viewDidUnload
self.someDelegate = nil;
...
}
[super didReceiveMemoryWarning];
}
But note that if you use ARC and iOS5+, you generally won't need to set your delegate to nil anymore, thanks to the weak property attribute and the Zeroing-Weak-References mechanism that automatically reset weak variables and properties to nil if the object they are pointing to does not exist anymore (thus avoiding dangling pointers).
[EDIT] And as explained by #Martin R in the comments, views are not unloaded anymore when receiving a memory warning in iOS6, so you won't have to manage this case of receiving a memory warning and think about releasing your delegate there as this use case won't occur anymore in iOS6.
Well, I did not come up with this myself, but if should help you: "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."
http://www.bgr.com/2012/06/11/ios-6-beta-download-link-iphone-ipad-ipod-touch-release/
Prior to iOS 6, we were supposed to
(void)viewDidUnload {
self.someDelegate = nil;
[super viewDidUnload];
}
Where did you hear this?
Do you even know what viewDidUnload is for prior to iOS 6?
viewDidUnload is only called in low memory situations, which cause the view to be unloaded. It will never be used during normal operation. If you were depending for it to be called to do other things, that is wrong.
Plus, why would you ever need to set self's delegate to nil anyway? "Niling the delegate" refers to setting other objects' delegates (which point to self) to nil when self is deallocated. Setting self's delegate makes no sense (why would you care setting stuff on self if self is no longer used?).

Resources