Table view images never being released - ios

I'm working on a major update to one of my applications and trying to cut down memory usage and making it cleaner and faster. I was using Instruments to profile the app and I was looking at the UIImage allocations, I have about 10 when the app starts out (although one is a status bar icon? Dont know why thats included). When i open up my Settings view controller (which is in a split view controller on iPad) it has basically an image with every table view cell, which is a lot. Presenting it for the first time adds 42 images. when I dismiss this view controller, there is still 52 images, when there should be only 10 now. If I present the controller again, there are now 91 images. This keeps going up and up. Instruments doesn't say there is a leak and I can't figure out what is happening. Each cell sets an image like
cell.imageView.image = [UIImage imageNamed:#"Help-Icon"];
How can I figure out why these images are not being released?
EDIT:
I think Its deallocating the images now. I changed from setting the imageView image directly to setImage:, so a table cell looks like this now:
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = NSLocalizedString(#"Homepage", #"Homepage");
UIImage *cellImage = [UIImage imageNamed:#"Homepage-Icon"];
[cell.imageView setImage:cellImage];
[MLThemeManager customizeTableViewCell:cell];
return cell;
MLThemeManager is a singleton that using a theme class to set properties of the cell to a theme, like the text label color, highlighted color, detail text label color, and background color.

The possible reason here is because of your Settings view controller was not deallocated (not released from memory). No matter how many images are in that view controller when view controller release from memory it will deallocates (remove) all image object from memory.
How to check your view controller is de allocated (release) or not?
Steps:
From Xcode long press on Run button. You get a small popup, Select Profile from that. And you can see new icon replaces the Run icon. (Similarly you can change it Run button to run the application).
Click on this button will start to profile your application.
Select Allocations section in Instruments window. There is a textfield above the allocation listing view. Click on that field and write your view controller name. (In your case SettingViewController).
You can see only filter result related to that key word you just type. Now go to Simulator -> Simulate your flow. Pop back from your view controller and check in Instruments that after leaving that view controller if it's still in the list. If it is there in a list than your view controller is not release from memory and each time you open that view controller will increase memory and never release until application is running.
How we can de allocate view controller?
I'll try to explain some way which I know to de allocated controller.
[ 1 ] Override dealloc method in your view controller and release objects in that. Mainly mutable variable and delegates. Put a debug breakpoint on -dealloc method and make sure it is called when you left that controller.
Note: If you have create global variable for class like UIPickerView, UIPopoverController, UIImagePickerController etc. and set delegates for that than these delegates must be nil in -dealloc method.
e.g.
Objective C code:
//---------------------------------------------------------------
#pragma mark
#pragma mark Memory management methods
//---------------------------------------------------------------
- (void) dealloc {
[self.tableView setDelegate:nil];
[self.tableView setDataSource:nil];
}
Swift code:
deinit {
self.tableView.dataSource = nil
self.tableView.delegate = nil
}
[ 2 ] Global object of subclass also needs to override -dealloc method and release relevant objects and delegates. Similar their dealloc method must have to be called.
e.g.
Check this scenario: Let's say you have created a subclass of UIView (or any other view) with the name MenuView. And you have created a global variable of this class in your view controller, than it's dealloc method will be called before view controller's dealloc method.
#property (nonatomic, strong) MenuView *menuView;
So here MenuView class needs to be override the -dealloc method and must be called before your view controller's dealloc method is called.
So that before leaving whole container its child views are release and removed from memory.
Check this in Instruments for MenuView class also that object is still their or not?
If it's dealloc method is not called than object of MenuView class is still in memory and your view controller class is referencing that object. So even if your view controller's -dealloc is called it will not deallocated because MenuView class object is live.
[ 3 ] You need to set weak reference to the IBOutlet. e.g.
Objective C code:
#property (nonatomic, weak) IBOutlet UITableView *tableView;
Swift Code:
#IBOutlet weak var tableView: UITableView!
Feel free to ask if anything is not clear.

Dont use the image asset... Copy the images to you a folder into your project an load it by code with imageWithContentOfFile it works to me

Try to use imageWithContentsOfFile to load image.
As rmaddy said,imageNamed will let your image stay in cache,this is the reason for your memory problem

Related

Code in viewDidLoad runs every time it is called

Hi all I am doing a course in Udemy, and the code calls for placing code in the viewDidLoad function as shown below:
override func viewDidLoad() {
super.viewDidLoad()
placesArray.append(["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"])
}
The array append should only run once, however, when I segue to another viewController and come back, it runs the code to append again. So I now have an array with 2 rows, both of which are Taj Mahal.
I thought that the viewDidLoad function only runs code once?
Is there a way around this?
Thanks.
Addendum:
I am using Swift, so I don't see any alloc and init while creating and launching the viewController. And weird as it sounds, the video tutorial has it working in the viewDidLoad and the trainer is using the storyboard to segue from the initial table view controller to a map view on a view controller and just has a back button on the map view that segue's back to the table view controller via the storyboard as well. - Could be because I have the latest version of the Swift language and the trainer was using an earlier version, (cause I noticed some slight differences in coding earlier) but you never know. Either way whenever he touches the back button it does not run the append code anymore.
I am trying to get in contact with the trainer as some of the suggestions here, though they are good don't seem to work.
I will put the solution in here once I get in contact with the trainer.
The viewDidLoad method is called when your view controller's view finishes loading. The view will load when a view controller's view property is nil and something attempts to access it.
UIViewController *myVC = [[UIViewController alloc] init];
UIView *aView = myVC.view; // this loads myVC's view; viewDidLoad is called when it completes loading.
If the view has unloaded (usually due to memory limitations), it will be called when the view property is next accessed.
Manipulation of data sets should generally not be done within view methods. Consider moving this to the init of the view controller (or to a different "datasource" class).
I suppose you are trying to do data initialisation in viewDidLoad. If there is no other operation on placesArray before viewDidLoad, then instead of append, what about setting the placesArray directly:
placesArray = ["name":"Taj Mahal", "lat":"27.175607", "lon":"78.042112"]
Then even if your view is unloaded for some reasons. Taj Mahal will still be added once only.
viewDidLoad is called whenever the view controller's view property is set. When does this happen? It depends on how the view controller is contained:
UINavigationController
- View Controller views are loaded as they are added to the navigation stack and "unloaded" (although the viewDidUnload method is deprecated) as they are removed.
UITabBarController
- View Controller views are loaded as they are added to the tab bar regardless of whether they are on screen or not. They stay loaded as you change from tab to tab.
Depending on your needs and use case, you can create your own view controller container that does what you need. Checkout the Apple docs on the proper way to do this:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

Dismissing/Delegate second view in a modal stack in storyboard

I have a TableView which describes a book with sections which represents the chapters and rows representing the verses.
A the top of this TableView I have a button in a navigation bar to allow "navigation".
The goal of this navigation button is to allow the user to easily jump to a given chapter/verse without scrolling manually (which can be very long).
When the button is pressed a tableview controller is called displaying all the available chapters of the book and when a chapter is selected another table view is called displaying a list of the available verses in the current chapter. Finally when the line is chosen the tablew view displaying the book should scroll to the given index/row.
So the idea : from the tableview representing the book I call the chapters view as modal and the verses as a push over the chapters view.
My problem is that I don't get the point of managing the delegate and dismissing from the 2nd modal view.
With 1 modal view I do things like that.
In the displayed VC (View Controller) I added the protocol and the delegate
#protocol ChapitresTableViewControllerDelegate <NSObject>
- (void)didDismissPresentedViewController;
#end
#interface ChapitresTableViewController : UITableViewController
#property (nonatomic, weak) id <ChapitresTableViewControllerDelegate> delegate;
#end
I have in the didSelectRow
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.delegate didDismissPresentedViewController];
}
in the displaying VC I add the following line
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ChapitresTableViewController *chapitresTableViewController = segue.destinationViewController;
chapitresTableViewController.delegate = self;
}
and of course
-(void)didDismissPresentedViewController
{
[self dismissViewControllerAnimated:YES completion:nil];
}
this would just work fine if I wanted to close after the first modal VC, but what I want is to have the second one being dismissed after I click in the second. Ok I can write the protocol and stuff in the second modal VC but how do I manage to have the delegate being send to the second VC.
Hope my question is clear enough it is not so easy to explain it.
Anyone understand me and can help me ?
NOTE : I know for now I don't pass any parameters back to the delegate, which I will do later to do the scroll. For now I just want to be able to close the second view, then I will add the required stuff to pass the parameters back to the delegate
I'm sure you can do this, but rather than modal view controllers with a navigation bar, wouldn't it be easier to use a navigation controller? Then you can use popToViewController to go back as many levels as you want to a particular view controller. You can either pass the UIViewController* of the various controllers you might want to pop to, or do so programmatically: e.g. How to pop back to specify viewController from navigationController(viewControllers/stack)?
In this scenario previous views controllers are retained. The ones you pop off are released (just like the modal ones you dismiss are released), but the ones that you pushed from are retained (just like the ones you presented from in a modal world are retained).
If the book is large, though, you'll have to be sensitive to memory usage. Thus, you will probably want to handle didReceiveMemoryWarning to release the model data for the previous views in either your modal sequence or push sequence, in which case, on viewDidAppear, you'll want to see if your app had to release the memory in response to didReceiveMemoryWarning and reload it in that case. But that's the desired behavior, either way, gracefully release the pages if needed (and reload them when the particular view reappears), but keep it in memory if you can.
Finally, you might also want to contemplate using UIPageViewController. Given what you've described, I'd like consider UIPageViewController first, UINavigationController and push segues second, and the use of modal segues third.

Segues and viewDidLoad/viewDidUnload method

I am making simple storyboard application which has 2 UIViewControllers and I am switching between them by using modal segue. Each UIViewController has one UIButton which is used to perform segue to another UIViewController. In viewDidLoad method I animate appearance of that UIButton on each UIViewController. I am using Cross Dissolve Modal segue.
When I press UIButton on 1st UIViewController I navigate to second UIViewController and animation is executed and 2nd UIViewController is shown. After I press UIButton on 2nd UIView Controller, first UIViewController is shown and it's animation is executed. Everything looks fine and viewDidLoad methods are called for each UIViewController when ever I navigate to it. And that's great.
I tried now to change Modal segue type from Cross Dissolve to other two by default offered in XCode Interface Builder. I changed to Cover Vertical, and everything worked just fine, without changes. But when I tried Flip Horizontal Modal segue, I saw a problem.
When performing Flip Horizontal Modal segue, my UIButton on both UIViewControllers is shown, but animation isn't executed. I tried debugging and I am sure that animation commands are being executed, but animation isn't shown.
So that's my first question: Does anyone know is there any difference between these types of Modal segues which may cause my animation not showing up?
Other questions are related to basic theory of segues and memory management. When I perform segue and navigate to some UIViewController, viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted? Or ARC is doing garbage collection behind the scenes?
If someone could explain how things works with storyboard segues and memory management/object lifecycle and why viewDidUnload method is never being called, I'd be very grateful.
[edit #1: Trying to unload UIViewController after performing modal segue]
[update #1: This shouldn't be done, viewDidUnload will be called automatically]
I am making segue in IBAction attached to UIButton click. I have written this peace of code to perform modal segue.
#try
{
[self performSegueWithIdentifier:segueToPerform sender:self];
}
#catch (NSException *exception)
{
NSLog(#"Exception: %#", exception);
}
#finally
{
[self viewDidUnload];
}
I have manually called viewDidUnload in #finally block and I have checked weather viewDidUnload is called in runtime and yes - it is called.
Does this mean I managed to unload my UIViewController object I created when navigating to it with modal segue from another UIViewController and remove it from memory?
Is this method regular as a replacement for:
[self dismissViewControllerAnimated:YES completion:nil];
because this above line returns me to UIViewController from which I navigated to current UIViewController, but that doesn't fit my needs, because I need to perform new segues from current UIViewController to other UIViewControllers (beside returning back to UIViewController from which I navigated to current one)?
[edit #2: Finish]
At the end I changed implementation model and loaded new UIViews under single UIViewController after I created separate XIB files for those UIViews. I have marked answer from #dasblinkenlight as the right one since it contains lots of useful informations and discussion on that answer gives good answers to some doubts about using modal segues.
I do not know the answer to the first part of your question, but once you learn the answer to the second part, I am sure that you would go with a different solution anyway.
viewDidLoad method is called every time. So, does that mean I created new object instance each time viewDidLoad method was executed?
Absolutely. "Modal" segue causes the new view to obscure the old one completely until the new view is closed. If you go back and forth many times, your code will accumulate a whole "stack" of views underneath the current one.
I also notice that viewDidUnload method is never called. So, if answer to previous question is affirmative (each viewDidLoad execution creates new object instance), does that mean that my UIViewController object instances are never being unloaded and deleted?
This is correct, all the view controllers that you create are still there, ready for you to close the views on top of it.
Or ARC is doing garbage collection behind the scenes?
ARC is not a garbage collector, it is a reference counting mechanism with a little automation from the compiler. The objects are still there.
You should change your code to call
[self dismissModalViewControllerAnimated:YES];
in the second controller, rather than using a modal segue that brings you back to the first one.
I hit a similar issue where I had a complex VC navigation web, and the resulting use of segues (no UINavigationController) was sucking up too much memory.
You may want to take a look at this question/answer to see my final solution.
The conversation here between uerceg and dasblinkenlight helped me in finding this solution.
Segues and clearing historical ViewControllers from memory

Custom UIViewController viewDidUnload never gets called

I have a custom UIViewController subclass that handles all the view initialization by itself (it doesn't use nib). There is also another UIViewController subclass loaded from nib. Both are contained by UITabBarController.
When a memory warning comes, the first controller does receive notification, but viewDidUnload doesn't get called. The second controller also receives notification and it's viewDidUnload does get called.
I checked in didReceiveMemoryWarning, self.isViewLoaded is TRUE and self.view.superview is null.
Both controllers (their tabs) are invisible at the time the notification appears.
Is there something special a custom view controller should do to be unloaded in a result of memory warnings?
If you are subclassing UIViewContoller and you do not initialize it from a NIB, you need to subclass the -loadView method. Otherwise iOS assumes that the view cannot be unloaded / reloaded.
It would be sufficient to just add the following to your implementation:
- (void)loadView {
[super loadView];
}
I will try to find a documentation quote for that.
The documentation is unfortunately not very clear on this matter.
I would check out the documentation on the View Controller Lifecycle. Specifically, check out the section on what happens when the memory warning is received. If your custom view controller's view is the view on screen, the OS will not attempt to release this view. Is this view on screen when you're getting the memory warning? In the simulator, navigate to the nib-loaded view and simulate a memory warning, see if your custom view gets released then. Also, see if viewWillUnload is being called. And make sure that in any of these methods that you're overriding that you call super.

ViewController viewDidLoad after presentModalView called?

I have a viewController called "FirstViewController". In an IBAction i call another ViewController called "thePageFlipViewController" and push it in sight via
[self presentModalViewController:thePageFlipViewController animated:YES];
after some time the user closes thePageFlipViewController with a button where the following code is executed via a delegate in FirstViewController:
[self dismissModalViewControllerAnimated:YES];
[thePageFlipViewController release];
And here is my problem:
-viewDidLoadin FirstViewController get's sometimes called after dismissing thePageFlipController. I don't understand why, because firstViewController should live in background. Is it dependent how long the modal view is displayed? is it possible that ARC does release something?
My problem is, that i initialise a lot of objects in viewDidLoad and the app crashes if viewDidLoad gets called again. I define some Routes for RESTKit there and RestKit complains that the routes are already set up and crash the app.
Any Help is appreciated.
When a view is not actually displayed it can be unloaded to free up memory. You would get a call to viewDidUnload: when that happens so you can release any objects you are holding strong references to. Then next time the view is needed, viewDidLoad: will get called again when the view is reloaded, there you have to recreate the objects you released in viewDidUnload:.
See the Memory Management section of the UIViewController class reference.
Also this answer has a good explanation already.

Resources