ViewController memory management (programmatically or using ib) - ios

I'm using Instruments to monitor my app's(ARC) memory usage, and found out that
When I pop a view controller from navigation view controller stack, its dealloc method is called.
But the memory usage only drops a little...(like 0.6M), and still much highter than before.
So here is the problem. The memory usage of my app keeps increasing....
How could I decrease the memory cost to the value before the view controller allocation.
Or at lease how could I release more memory.
Also, it seems that the memory cost is much highter when using xib(storyboard).
Should I set the data to nil like someArr = nil in dealloc method?
I have no idea. Anyone help please! Thank you!!
This image is captured when one view controller is allocated, and still alive after its dealloc method is called.......

You can try using #autoreleasepool{ } blocks, though be aware that the memory footprint doesn't necessarily return all the way to where it started. A big caveat is that if dealloc is already called, using autoreleasepool in your code might do nothing because the VC was already released properly.
Though you can consider where to use it within the view controller implementation, perhaps within a dealloc implementation for the VC. To ensure that when the VC's dealloc is called, objects that receive an autorelease within the #autoreleasepool block will be released immediately.
Snippet of mine from OSX code:
- (void) dealloc
{
#autoreleasepool {
// End KVO
[self stopObservingIndexPath];
for (NSView* subView in _treeSubViews)
[subView removeFromSuperview];
[_categoryView removeFromSuperview];
[self.view removeFromSuperview];
_treeSubViews = nil;
_categoryView = nil;
}
}
... that was written in a quest to reduce footprint like yours, by forcibly dismantling the view hierarchy.
And the Apple reference here.

Related

View Controllers accumulate memory even when dismissed, in Objective C

I can't seem to find any resolutions to this. I've made all my objects 'weak' in both view controllers in question. I have included dismiss functions and even the 'RemoveFromSuperView' function. I tried them all without luck. I also tried making the button action 'modal', 'push', ect. None made any difference.
Essentially as I move from controllers, memory seems to just accumulate more and more. On both controllers I'm simply using WebView's. I rack up over 100MB of memory usage after some time navigating between views. Eventually the app runs out of memory and crashes.
How do I either clear all memory accumulated by the app or properly dismiss/kill all inactive View Controllers and clear all memory associated?
This is how you override dealloc method
- (void)dealloc {
[_object release];
[super dealloc];
}
where _object is whatever property you initialized.

Memory issues with iOS keyboard extension [duplicate]

Custom KeyBoard get terminated due to memory pressure in iOS 8
Initially my custom keyboard is taking around 25mb of memory, but this memory is not deallocated with I dissmiss the keyboard. Memory keep on increase when we open custom keyboard again and again and finally terminated due to memory pressure.
Help me out with this issue?
You can dealloc some things in ViewWillDisappear function of KeyboardViewController
The keyboard extension runs in a process that persists after the keyboard disappears. Your keyboards view controller is created anew each time your keyboard is created, but the process that view controller is in persists. So free memory when your view controller is closed. If you are using images you won't want to use imageNamed: you will want to use imageWithContentsOfFile:. Because UIImage uses a cache for imageNamed that will persist.
I have tried tons of ways to avoid this famous memory accumulation issue, but according to my long long trial & errors, the best and the simplest way to free all memory before a keyboard disappears is to call exit(0) in viewWillDisappear of KeyboardViewController.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
exit(0);
}
[Update] exit(0) was perfect to release all memory since it kills the keyboard extension process. Unfortunately it seems like killing the process makes iOS unstable.
Consequently, the most stable way is to release all allocated objects as much as possible in viewWillDisappear. For example,
For all custom views and all custom view controllers
Remove all strong references of the views and the view controllers, such as subviews, constraints, gestures, strong delegate, and so on.
[aView removeFromSuperview];
[aView removeConstraints:aView.constraints];
for (UIGestureRecognizer *recognizer in aView.gestureRecognizers)
[aView removeGestureRecognizer:recognizer];
Set nil to all object properties of the view controllers.
aViewController.anObject = nil;
For other big custom objects
Remove all added objects from all arrays, dictionaries, and so on.
[anArray removeAllObjects];
Do not cache images using imageNamed:.
If well released, memory usage while debugging would not be increased or very slightly increased(<0.1MBytes per dismissing). If memory usage is increased after many dismissing even though custom objects are released as much as possible, exit(0) can be called periodically with some risk of unloading.

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)
}

UIPageViewController crashes when flipped too fast during low memory

I had some memory problems due to Xcode's template for a UIPageViewController caching all the page data, so I changed it to load the pages dynamically, so now when my app receives a low memory warning, it releases the memory for page's not showing, but if the user is flipping through the pages real fast by tapping the edge of the screen, it still crashes. I'm guessing this is because it can't free the memory fast enough when didReceiveMemoryWarning gets called. If the user is flipping slowly, it works fine. I limited the speed at which the user can flip pages, but it still happens. I want to be able to free up the memory every time the page is turned, and not have to wait for a low memory warning. I'm using ARC. Is there a way to do this? Or what else can I do to prevent this? Thanks.
EDIT:
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if (index == NSNotFound || index == MAX_PAGE_INDEX) {
return nil;
}
return [self viewControllerAtIndex:++index];
}
I think you hypothesis is correct, since I also experienced a similar behavior: when you flip to the next page, also for the sake of animating things nicely, the new page is allocated before the old one is deallocated and it takes some times for the old one to be deallocated. So, when you flip fast enough, objects are allocated faster than they are deallocated and eventually (actually, pretty soon), your app is killed due to memory usage. The deallocation delay when flipping pages becomes pretty obvious if you follow the allocation/deallocation of memory in Instruments.
You have three approaches to this, IMO:
implement a "light" viewDidLoad method (actually, the whole initialization/initial display sequence): in some app, it makes sense, e.g., to load a low resolution image instead of the high resolution image that will be displayed; or, slightly delaying the allocation of additional resources your page need (db access, sound, etc.);
use a pool of pages, say an array of three pages (or 5, it depends on your app), that you keep "reusing" so that the memory profile of your app remains stable and avoid spikes;
careful review the way you allocate and release memory; in this sense, you often read that autorelease add some "inertia" to the release/deallocation mechanism, and this is pretty easy to understand: if you have an autoreleased object, it will be released by its release pool only when you cycle through the main loop (this is true for the main release pool); so, if you have a long sequence of methods that are called when flipping page, this will make the release/dealloc happen later.
There is no magical bullet when it comes to memory usage optimization, it's a pretty detailed and hard work, but IME you will be able to reduce the memory profile of your app if you review your code and apply those 3 guidelines. Especially, inspecting the spikes of memory allocation in Instruments and trying to understand to what they relate is extremely powerful.
Here is an additional change I made, which someone might find helpful:
Basically, I only allow a new page turn to begin if the previous one has finished.
I'm using apple's default PageViewController project as a template so I'll use terms defined in that project.
Whenever a page VC is requested through viewControllerAtIndex:, I set a boolean value on ModelController called 'shouldDenyVC' to YES.
In my EbookViewController which is the UIPageViewController's delegate, I capture the gesture recognizers, and assign EbookViewController as their delegate:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
for (UIGestureRecognizer *gr in self.view.gestureRecognizers) {
gr.delegate = self;
}
Then, I'm able to deny a page turn by denying the gesture recognizers:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch
{
if (_modelController.shouldDenyPageTurn == YES) {
return FALSE;
}
return TRUE;
}
And finally, I set _modelController.shouldDenyPageTurn = NO at the end of the UIPageViewController delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
I also had to set _modelController.shouldDenyPageTurn = NO at the end of any preloading so that page turns are allowed off the bat.
There is a bug in iOS5 at the moment that causes a scroll view to leak a small amount of memory.
Have you tried profiling your application in instruments checking for allocations and memory leaks?
You can simulate a low memory warning either in the simulator (hardware -> simulate low memory warning). Or you can do it via code, (just remember to remove after debugging because this will get your app rejected!)
[[UIApplication sharedApplication] performSelector:#selector(_performMemoryWarning)];
If you are using strong or retain properties then set them to nil after you are done with them and ARC will release the memory they are pointing to behind the scenes.
If you are creating lots of temporary objects (objects that are not properties or not alloc'd) then insert an autorelease pool:
#autoreleasepool {
}
And finally, show some code and we can better help you.
It might be caused by Rendering. When flipper too fast, the memory and CPU used by redrawing the "Page" will increase rapidly. If the views you used in UIPageViewController is based on CALayer and have too many pages, flipping too fast will definitely crash the App.
One solution is to customize the layer and cache the rendering result. Re-render the content only when must. But the cache might increase memory usage.
Since you didn't post any code it's hard to guess where exactly lies your problem.
To force unloading a view you can override viewDidDisappear: method of those viewcontroller classes that are appearing in UIPageViewController.
Code would look like:
- (void)viewDidDisappear:(BOOL)animated {
[self didReceiveMemoryWarning];
}
If you also have didReceiveMemoryWarning overriden don't forget to call [super didReceiveMemoryWarning]; from it.
There can also be some confusion on how UIPageViewControllerDataSource methods work - you might have some 'mixed wires there'. Check accepted answer here.

Resources