How to recover from viewDidUnload(after memory warnings), using UISplitViewController - ipad

I'm making a split-view based iPad application(Portrait mode only), and I want to know how to recover initial state after viewDidUnload is called.
When split-view application started for the first time,
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController:
is called automatically (right after -viewDidLoad).
I prepares UIBarButtonItems in the method.
If I open modal dialog or something with UIWebViewController (it consumes a lot of memory), application receives memory warning, viewDidUnload(s) are called.
When I close the modal dialog, -viewDidLoad is called automatically, but this time
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController: is not called.
I prepares UIBarButtonItems in
-splitViewController:willHideViewController:withBarButtonItem:forPopoverController:
but it is not called, so buttons are dismissed.
In that case, should I call the method manually?
I found similar posting here.
https://github.com/grgcombs/IntelligentSplitViewController/issues/6
Thanks.

I don't know it is OK to answer to my own question, but maybe I found an answer for this. http://osdir.com/ml/cocoa-dev/2011-02/msg00430.html
It says that we should preserve BarButtonItems in viewDidUnload, and load it in viewDidLoad.
It seems working fine.
- (void)viewDidUnload {
[super viewDidUnload];
self.toolbarItems = self.toolbar.items; // property with retain policy
}
- (void)viewDidLoad {
[super viewDidLoad];
if (self.toolbarItems) {
self.toolbar.items = self.toolbarItems;
self.toolbarItems = nil;
}
}

Related

Memory not being released right even dealloc method is called after dismissing view controller

What am I doing is I am creating lots of UIView in the background and keep them in a NSMutableArray to use later. But when I dismiss the view controller I check the memory in Xcode and it seems some of memory not being released. I checked; view controller is being deallocated.
Check please:
This happend after several showing and dismissing the view controller. Some of them is being released but not all.
Thanks.
Uncheck Enable Zombie Objects option under Edit Scheme. And try again.
A zombie is an object that has been deallocated, but references to it still exist and messages are still being sent to it
I think this link has more info for you
What is NSZombie?
I suppose you use arc, so it might be useful to explicitly release this in dealloc.
-(void)dealloc {
for(UIView *vw in self.arrayOfViews) {
vw = nil;
}
self.arrayOfViews = nil;
}
Using dealloc is a bit like the old days (pre-arc), but it will help you manage memory better.
!important! --> NEVER call [super dealloc]; when using arc!
In dealloc method release all views that you have in the array.
called the below method in your controller dealloc method
- (void)releaseViewArray
{
// Releasing views in the array
for (UIView *view in _viewArray) {
[view release];
}
// Releasing the array that holding the views
[_viewArray release];
}

iOS app Memory Usage Issue

Hello I have a memory issue in my iPad app. Each time I change from a view to another view (this transition is made with segues), the app is increasing the memory used and never releases the memory. It is always increasing the memory used.
Let's see an example:
I am in my first view "home" which has these lines in viewDidLoad and viewDidAppear
(void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background.png"]];
[self initializeHomeDataSources];
DateService* dateService = [[DateService alloc] init];
self.currentDate = [dateService today];
[self checkHomeStatus];
[self showEmptyHomeViews];
[self setUpFonts];
}
and this my view did appear method
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
_homeAutomaticUpdate = YES;
//This is a Thread
[NSThread detachNewThreadSelector:#selector(automaticHome) toTarget:self withObject:nil];
[self.phrasesView startPhrasesThread];
if ([InternetService internetConnection]) {
[self synchronizeHome];
}
if (self.scheduleDataSource.currentEvent) {
[self loadMessagesFor:self.homeDataSource.currentEvent];
[self loadLibraryFor:self.homeDataSource.currentEvent];
} else {
[self loadLibrary];
}
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
_homeAutomaticUpdate = NO;
}
All the IBOutlet's are defined as (nonatomic, strong).
Each time the HomeView is loaded the memory increases it's quantity and I don't know what is happening.
Can anybody help me here? This problem is causing me consternation.
I'm guessing that you're going "backwards" to previous controllers using segues. Is that true? If so, that's your problem -- unless you use an unwind segue, you should never go backwards using segues because they always instantiate new controllers. So, when going back to previous controllers, either use an unwind, or use code that reverses your forward segues -- that is, popViewControllerAnimated: if the forward segue was a push, and dismissViewControllerAnimated:completion: if the segue was a modal.
Few questions:
Is your app killed after a while, because of memory usage?
Why you are creating new thread in -viewDidAppear?
Have you tried to simulate memory warning?
(In simulator: Hardware -> Simulate Memory Warning or Shift + CMD + M)
Does the memory gets down after memory warning or not?
This is not a whole answer for your question but your outlets must be weak unless their not top level objects.
All the IBOutlet's used should be (nonatomic, weak). Try this out..

[CustomViewController respondsToSelector:]: message sent to deallocated instance

This is used to work fine for my pre-ARC code, but since refactoring all the project to be ARC compatible, I start getting this crash:
[CustomViewController respondsToSelector:]: message sent to deallocated instance
My project is an iPad app with a split view, but contrary to apple documentation, previous developer has put another view controller to be visible on app launch before split view. So I know this is not the right way to do, but as I said it used to work before ARC integration so I need to get a workaround with this.
The root view controller which contain a menu of items, each item display a detail form to be filled, then a click on next button move to the next detail screen, etc.
The issue starts when I click on home button put on root view to get back to the home view, here is the relevant code that move the user to the home screen:
//this method is in the appdelegate, and it gets called when clikc on home button located on the root view
- (void) showHome
{
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
Then when I click on a button to get back to the split view (where are the root/details view), the app crashes with the above description. I profiled the app with instruments and the line of code responsible of that is located in the RootViewController, in the didSelectRowAtIndexPath method, here is the relevant code:
if(indexPath.row == MenuCustomViewController){
self.customVC=[[CustomViewController alloc] initWithNibName:#"CustomVC"
bundle:nil];
[viewControllerArray addObject:self.customVC];
self.appDelegate.splitViewController.delegate = self.customVC;
}
customVC is a strong property, I tried to allocate directly and assign to the instance variable but that didn't help to fix the crash. Any thoughts ?
EDIT:
Here is the stack trace given by instruments:
[self.appDelegate.splitViewController setViewControllers:viewControllerArray];//this line caused the crash
[viewControllerArray addObject:self.appDescVC];//this statement is called before the above one
self.custinfoVC=[[CustInfoViewController alloc] initWithNibName:#"CustInfo" bundle:nil];//this statement is called before the above one
self.appDelegate.splitViewController.delegate = self.appDescVC;//this statement is called before the above one
custinfoVC and appDescVC are strong properties.
I solved this problem by setting my delegates and datasources to nil in the dealloc method. Not sure if it'll help you but its worth a try.
- (void)dealloc
{
homeController.delegate = nil;
//if you have any table views these would also need to be set to nil
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
You may want to setup the CustomViewController during app launch, and display the other views modally on top if necessary. The error message you're getting is because something is getting released by ARC prematurely. It might have not manifested before because pre-arc stuff wasn't always deallocated immediately. ARC is pretty serious about releasing stuff when it leaves scope
Hard to tell without seeing a lot more of the code involved, and what line it breaks on, etc.
This:
- (void) showHome {
//THIS: where is homeController allocated?
homeController.delegate = self;
self.window.rootViewController = homeController;
[self.window makeKeyAndVisible];
}
EDIT:
Add this line right above the line that causes your crash
for (id object in viewControllerArray) {
NSLog(#"Object: %#",object);
}
I had the same Problem.If you are not using "dealloc" method then use "viewWillDisappear" to set nil.
It was difficult to find which delegate cause issue, because it does not indicate any line or code statement for my App So I have try some way to identify delegate, Maybe it becomes helpful to you.
1.Open xib file and from file's owner, Select "show the connections inspector" right hand side menu. Delegates are listed, set them to nil which are suspected.
(Same as my case)Property Object like Textfield can create issue, So set its delegate to nil.
-(void) viewWillDisappear:(BOOL) animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]){
self.countryTextField.delegate = nil;
self.stateTextField.delegate = nil;
}
}

UIViewController lifecycle calls in combination with state restoration

I'm trying to implement state restoration in an app that uses iOS 6+ and storyboards, but I am having problems finding a way to prevent duplicate calls to heavy methods.
If I simply start the app, then I need to setup the UI in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
This works fine in a normal, non-state-restoration world. Now I've added state restoration and after restoring some properties I need to update the UI with those properties:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
// restore properties and stuff
// [...]
[self setupUI];
}
So what happens now is that first the setupUI method is called from viewDidLoad, and then again from decodeRestorableStateWithCoder:. I don't see a method that I can override that's always called last.
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder
viewWillAppear
viewDidAppear
I can't place the call to setupUI in viewWillAppear because then it would also be executed every time you native back to a view.
It would be much handier if decodeRestorableStateWithCoder was called BEFORE viewDidLoad because then you could use restored properties. Sadly that not the case, so... how can I prevent doing the work in viewDidLoad when I know that I need to do it all over again in decodeRestorableStateWithCoder right after?
If you're doing state restoration programatically (i.e. not using storyboards), you can use + viewControllerWithRestorationIdentifierPath:coder:, init the view controller there and use whatever you need from the coder to do your pre-viewDidLoad initialization.
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([[identifierComponents lastObject] isEqualToString:kViewControllerRestorationIdentifier]) {
if ([coder containsValueForKey:kIDToRestore]) {
// Can only restore if we have an ID, otherwise return nil.
int savedId = [coder decodeIntegerForKey:kIDToRestore];
ViewController *vc = [[ViewController alloc] init];
[vc setThingId:savedId];
return vc;
}
}
return nil;
}
I've found that trying to implement state restoration has shown up bad programming practices in my code, like packing too much into viewDidLoad. So while this works (if you're not using storyboards), the other option is to refactor how you're setting up your view controllers. Instead of using a flag, move code pieces to their own methods and call those methods from both places.
#property (nonatomic) BOOL firstLoad;
- (void)viewDidLoad {
[super viewDidLoad];
self.firstLoad = YES;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.firstLoad) {
[self setupUI];
self.firstLoad = NO;
}
}
Thanks to #calvinBhai for the suggestion.
Funny enough the decoding sequence is even different and exactly:
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
and it totally makes sense like this.
From the book "Programming iOS 9: Dive Deep into Views, View Controllers, and Frameworks" pages 386-387
The known order of events during state restoration is like this:
application:shouldRestoreApplicationState:
application:viewControllerWithRestorationIdentifierPath:coder:
viewControllerWithRestorationIdentifierPath:coder:, in order down the chain
viewDidLoad, in order down the chain; possibly interleaved with the foregoing
decodeRestorableStateWithCoder:, in order down the chain
application:didDecodeRestorableStateWithCoder:
applicationFinishedRestoringState, in order down the chain
You still don’t know when viewWillAppear: and viewDidAppear: will arrive, or whether viewDidAppear: will arrive at all. But in applicationFinishedRestoringState you can reliably finish configuring your view controller and your interface.
Yes, it would indeed be nicer if -decodeRestorableStateWithCoder: were called before -viewDidLoad. Sigh.
I moved my view setup code (which depends on restorable state) to -viewWillAppear: and used dispatch_once(), instead of a boolean variable:
private var setupOnce: dispatch_once_t = 0
override func viewWillAppear(animated: Bool) {
dispatch_once(&setupOnce) {
// UI setup code moved to here
}
:
}
The documentation states that "views are no longer purged under low-memory conditions" so dispatch_once should be correct for the lifetime of the view controller.
Adding to berbie's answer,
The actual flow is:
initWithCoder
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
Be aware that inside initWithCoder, you need to set self.restorationClass = [self class]; This will then force viewControllerWithRestorationIdentifierPath:coder: to be called.
I noticed that setting the splitViewController.delegate in willFinishLaunchingWithOptions causes viewDidLoad to be called even earlier. So if you move that to both didFinishLaunchingWithOptions then you can successfully configure your view controller inside - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder before viewDidLoad is called. It might be useful for you to do it there anyway since you'll have access to AppDelegate objects like persistentContainer.viewContext rather than need to register that object with restoration so it could have been accessed by reference in the ViewController's - (void)decodeRestorableStateWithCoder:(NSCoder *)coder.
One correction to MixedCase flow (which was very helpful, thank), the actual call flow is a bit different :
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
viewControllerWithRestorationIdentifierPath (decode any data that is needed for regular start-up)
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
decodeRestorableStateWithCoder (decode restorable state data, and set your controller UI)

Best place to change UINavigationBar property?

I want make some changes in UINavigationBar property programmatically. - viewDidLoad: or -initWithNibName: is the best place to do this and why?
– viewDidLoad is the ideal function to perform these kind of changes.
As the name suggests the view will be completely loaded into the memory when this function is called and should not be any problem to make these kind of changes.
I wouldn't suggest making such changes in – viewWillAppear: or – viewDidAppear: as they will be called each time the view appears. This would be helpful if you would want to change the navigation bar properties each time it appears. I think that situation is highly unlikely and hence suggest – viewDidLoad as it will be called only once.
-initWithNibName: will create your controller instance from the nib file. You can perform the operations you have mentioned in this function, but there are chances it might act weird sometimes as it might not be in the right state. Even Apple Docs suggest "If you want to perform additional initialization after the nib file is loaded, override the viewDidLoad method and perform your tasks there."
If yours viewControllers have different property of navigationBar you can change they next way. In UIViewController subclass override next methods:
- (void) viewWillDisappear: (BOOL) animated
{
[super viewWillDisappear: animated];
[self.navigationItem setTitleView: nil];
}
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
[self.navigationItem setTitleView: titleView];
}
It works.

Resources