5

I have an iPad app which has a lot of screens and a lot of segue options. At the moment, I am simply using performSegueWithIdentifier to initiate these segues, and my fear is that I'm taking up a lot of memory as the user performs more and more segues. I've seen that people recommend using the function popToRootViewControllerAnimated: if using a UINavigationController, but the problem is that I'm not using one. How can I stop the number of VC's proliferating? The way the app works, the user does constantly return to the root VC - effectively a search screen. So if I could clear the stack of VC's when such a segue is needed, that would solve my issue I think, but I have no idea how to go about this. Thanks for any suggestions.

Scoop
  • 411
  • 5
  • 10

4 Answers4

10

When you are using segues the flow moves backwards and forwards. When the user moves backwards (ie presses "back") then it will not push to a new VC but it will pop to a VC that already existed. When you pop, the current VC is removed from the stack and memory.

If you have segues to move backwards in the flow then this is wrong. You only need segues to move forward.

A PROPER PREPARE FOR SEGUE

In prepare for segue you should never create your own view controllers and push to them. The storyboard is there to do all of this for you.

A proper prepareForSegue method should look something like this...

- (void)prepareForSegue:(UIStoryBoardSegue*)segue
{
    if([segue.identifier isEqualToString:"SomeSegue"])
    {
        MyNewViewController *controller = segue.destinationViewController;

        controller.someProperty = "some value to pass in";
    }
}

That is all you need. Note that you only need this if you intend to pass some information to the new view controller. If you are not passing anything forward then you don't need this method at all.

When the method ends the new VC will get pushed onto the screen by the storyboard file.

UNWIND SEGUES

If you have a random flow (like in your comment) then you can use unwind segues to achieve this.

In you 'A' view controller have a function like...

- (IBAction)someUnwindAction:(UIStoryboardSegue*)sender
{
    //some action to run when unwinding.
}

It needs to receive a UIStoryboardSegue object. If set up as an IBAction you can also access it from Interface Builder.

Now when you want to go A > B > C > B > A then just used the standard push and pop (from the segue and back button).

When you want to go A > B > C > A then you can use the unwind segue from controller C.

If you have a cancel button or something in controller C and this is in the Interface Builder and this should take you back to controller A. Then in the Interface Builder underneath controller C you will have a little green square with a door and an arrow pointing out of it. Just point the action of the cancel button to this symbol and choose "someUnwindAction". (Note, unwindAction is in A, button is in C.) XCode then uses this to pop you all the way back to A and deals with removing any memory and stuff. If you want you can send additional information back to A too.

If you want to access this unwind segue from C programmatically then you can run...

[self performSegueWithIdentifier:"someUnwindAction" sender:nil];

This will also pop back to A.

Fogmeister
  • 70,181
  • 37
  • 189
  • 274
  • I am indeed using segues to move in all directions currently. But the navigation is not limited to simple forwards and backwards. You can move from A > B > C > A or A > C > B > C > B > A for example. – Scoop Jan 01 '13 at 11:08
  • I didn't know about the ' MyNewViewController *controller = segue.destinationViewController; ' trick. So I guess you're saying the storyboard automatically creates the VC instances for me? And I just need to point to the correct one in prepareForSegue:? I do indeed have data that I need to pass between these VC's, that is why I'm implementing that method. – Scoop Jan 03 '13 at 01:05
  • Yes, that's correct. The storyboard creates the VC for you and pushes it for you too. All you are doing here is intercepting this and adding more information after it gets created but before it gets pushed. If you have any code inside "initWithNibName" then you will need to move this elsewhere as this function is not called. – Fogmeister Jan 03 '13 at 01:37
  • Oh, and this isn't a "trick" it is the way segues are supposed to be used :D if you are creating your own VCs then you are effectively creating two VCs for every segue. This is the same when you use the code that you have in the other answer. You may be getting back to where you want but you will probably find that you have unused and unseen VCs stranded around the place. – Fogmeister Jan 03 '13 at 01:42
  • Apologies for delay in response. I think I understand the unwind segues now and agree this would be a nice clean way to achieve my goal. I do have one more problem though. I'm initiating the segue via a UIImage (programmatically calling the segue via a UITapGestureRecognizer technique). It appears to me that I can't link the UIImage as it doesn't support an action attribute) to the Exit (and hence the unwindSegue). I tried just calling the unwindSegue programmatically, but this results in an error. Any ideas? I did try linking a dummy button to the Exit and pressing the button works. – Scoop Jan 06 '13 at 02:13
  • You can call the unwind segue just like a normal segue (IIRC) just use performSegueWithIdentifier – Fogmeister Jan 06 '13 at 03:12
  • This is the call I'm making: [self performSegueWithIdentifier:@"unwindSegueForSearchScreen" sender:nil]; This is the error I get (this is even when I have a dummy button on that VC linking to this unwind segue): *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'unwindSegueForSearchScreen''. – Scoop Jan 06 '13 at 07:05
  • ok figured out how to link the unwind segue to the VC thanks to this answer http://stackoverflow.com/questions/12509422/how-to-perform-unwind-segue-programmatically?rq=1 . thanks for your help Fogmeister – Scoop Jan 06 '13 at 08:32
  • @Fogmeister wouldn't it be better to set controller = nil; after you set the property to remove one of the references to the VC? – Leonardo Amigoni Feb 23 '16 at 04:39
  • @LeonardoAmigoni I don't understand what you mean? Which controller? – Fogmeister Feb 23 '16 at 09:49
  • @Fogmeister If i move from C>A (by unwind segue) will my B controllers removes from memory – siva krishna Nov 30 '16 at 13:13
  • @sivakrishna yes. That is correct. An unwind segue will remove any view controllers between the current one and the one you unwind back to. – Fogmeister Nov 30 '16 at 13:14
  • But I'm getting memory leak when run in the device, in simulator its working – siva krishna Dec 01 '16 at 16:42
  • @sivakrishna there could be a million different reasons for getting a memory leak. If you want help with it, add a question. A comment is not the place to fix this. – Fogmeister Dec 01 '16 at 17:02
  • http://stackoverflow.com/questions/40844571/how-to-come-back-to-tabbar-controller-after-presenting-few-modal-view-controller?noredirect=1#comment68907558_40844571, this is my scenario, Tabview with 4 controllers let's say A,B,C,D. And "E" is a sub class of tabbarcontroller. Now from E I'm presenting F-G-H-I. All are presented modally. Now I unwind from " I - E " which is tabbarcontroller. Is this correct way – siva krishna Dec 01 '16 at 19:38
1

imho I don't see any issues with using segues, it is much more simple than anything else. If you worry about memory consumed then just profile your app and see how much it eats and how often your "memory pressure handler" is called.

Cynichniy Bandera
  • 5,747
  • 2
  • 26
  • 31
  • 1
    Based on analytics tracking from real life users, the memory warning is being hit often. Only for some users. But many more times than the number of segues being performed. – Scoop Jan 01 '13 at 11:31
  • without more details it is hard to say what is it. Profile it and see what objects eat memory. It may be some huge size image that you use for background... or something else. – Cynichniy Bandera Jan 01 '13 at 11:34
  • I ran a profile. I'm new to reading these so its hard to tell what has the memory, but it does increase with every segue. my main search screen does have a grid display with images on it. I assume that when I segue, the viewDidUnload method is not called? Hence none of the cleanup is occurring? Should I be clearing up most of the elements that viewDidUnload cleans up in the prepareForSegue method maybe? It would be preferable to just remove the VCs totally from the stack as I can't figure out how to reuse them, but otherwise the special memory cleanup is the next best thing I guess. – Scoop Jan 01 '13 at 21:58
  • This is absolutely wrong. You should never use segues to both push and pop between VCs. You will get memory errors all over the place. You should be using unwind segues if you want to pop back a number of VCs at a time. – Fogmeister Jan 02 '13 at 20:45
0

ok I believe I've figured this out. It appears to work.

Since I keep returning to a common VC, I am placing code in the places where I want to segue back to this 'root' VC to clear the VC stack.

I still perform the actual segue in my normal code

[self performSegueWithIdentifier:@"editBackToSearch" sender:self]; 

However in the 'prepareForSegue:' method, I no longer create a new VC object, instead I perform any work I have outstanding (save my data), and then clear the VC stack with the following code.

UIViewController *vc = self;
while ([vc presentingViewController] != NULL)
{
    vc = [vc presentingViewController];
}   
[vc dismissViewControllerAnimated:NO completion:nil];

It appears to run smoothly and has the desired impact (confirmed via the profiler) of releasing the memory that these VC's sucked up.

One last comment - for some reason I could not animate the dismissal command, this appeared to trigger an infinite loop and resulted with an EXC_BAD_ACCESS. With animation set to NO, it works great though.

Thanks for your help.

Scoop
  • 411
  • 5
  • 10
  • No, this is wrong. Not been on for a couple days. Will edit my post. – Fogmeister Jan 02 '13 at 20:36
  • Hmm... Also, you shouldn't ever be CREATING view controllers in the prepareForSegue. I'll add this to my answer too. – Fogmeister Jan 02 '13 at 20:46
  • Based on my testing to date and other discussions on this topic I don't believe that what I'm doing here is wrong. Refer here for example: http://stackoverflow.com/questions/11035809/pop-the-current-view-using-segues-storyboard-on-ios-5 . Appreciate the alternative option you provided though and if I find I'm running into issues I'll give that a try. I will add clarification that I am using modal segues. – Scoop Jan 03 '13 at 01:02
  • The code I provided is straight from several WWDC talks and also the problem you are having is why they added unwind segues in the first place :) You will find that it clears up a lot of your code too. I'd recommend not keeping what you have in order to make everything clearer in the future. You are currently jumping through hoops that aren't there any more :D – Fogmeister Jan 03 '13 at 01:39
0

Swift 3: this is to prevent memory leaks in Swift 3 & 4

@IBAction func entryViewSelected(_ sender: UIButton) {

    self.dismiss(animated: true, completion: nil)
}
aremvee
  • 179
  • 1
  • 12