Basic question:
Is there a reliably way to trigger showing modal UIViewControllers at any point in the app's lifetime (including from different threads)?
My current approach is to call presentViewController on the showing ViewController (found through window.rootViewController + hierarchy traversing but that's unimportant). This generally works, but is sometimes ignored due to things like a navigation action/animation taking place.
E.g. a background thread signals for a popup to be shown, and presentViewController is called on a ViewController in the process of being dismissed.
I've tried a few work arounds such as repeating the signal if the ViewController isn't shown (which led to some instances of it being show multiple times), but it's ended up being a game of whackamole.
An ideal solution would also allow navigation to take place underneath the popup, but the primary issue right now is just reliability.
edit
To be clear, I’m a seasoned developer. The threading is being handled properly, the instance and type management is working. My problem is trying to manage all
the corner cases, not the basics of how to do it.
If you need mechanism for thread safe showing different VCs in multithread environment, you can make some object which is responsible for presenting/dismissing controllers. And make some queue on presenting/dismissing. So when your signal occurs, your operation on presenting will be next after dismissing current VC in queue
Related
I've noticed that as I navigate across my iOS app memory stack grows with each opened viewController.
My navigation is based on segues and I do self.dismiss() before performing each segue. Still, it looks like viewControllers stack in memory.
How can I avoid this?
In Android finish() kills the Activity (in most cases), so I need an alternative in the iOs.
The memory issue can have several causes and not necessarily a UIViewController. In order to find and solve the issue you have to reduce the scope of the issue from "app" to a certain screen or even class. Then you can check the code and try to figure out where's the suspicious code.
Solving this issue is not a straight up task and there's no "how to fix memory issue for my app" tutorial, you'll have to check your code and compare with potential causes of memory leaks.
Also you'll have to be careful for false positives of memory leaks. Here are some steps I follow when I suspect a memory issue.
Navigate trough the app "till the end", then go back to "home screen", if the memory drops, all good.
If the memory doesn't drop, I do the same navigation several times, if the memory increases with the same step (more or less but close) then there's an issue. If the memory doesn't increase (maybe just a bit, several kb) then it's ok, it means there are some assets cached in memory (images, files, etc). But you will need a way to test this scenario too.
Now we are back to the "memory increased again almost the same as first time", now I do a clean run, and take each screen at a time, I just open the screen go back (dismiss/pop the controller) and observe, if the memory drops then that's not the screen that leaks. When I find the screen that increases the memory and never goes back:
check if the controller is passed as a reference to other objects that won't be deallocated (singleton classes or other, depends on the app).
check if the controller is sent as "delegate" to any other classes and if those delegates are correctly defined (strong delegates are a biiiiig issue).
if all of the above are ok, then I'll simply comment all the code that performs any work and try again. If commenting the code doesn't drop the memory(this happens rarely) then the screen is not the right one.
if the memory drops after commenting the code, I start de-commenting bits of the code and run again, until you'll find the piece of code that creates you issues.
One thing to keep in mind, you have to be patient while investigating memory issues, and to be honest, sometimes you have to be lucky too.
Per documentation on UIViewController.dismiss:
Dismisses the view controller that was presented modally by the view controller.
So calling this would dismiss any view controller shown modally. Based on your question, I have to assume that your segues are push segues and not modal, otherwise you'd be seeing your view controllers disappear.
Your 'view controller stack' might be with regards to the navigation stack on a UINavigationController. If so, then those view controllers remain in memory, as when a view controller is popped off the stack (ie: user swipes from left edge of screen, or hits "Back" in the nav bar), the previous view controller appears.
Without more information on how you're presenting your view controllers or building your navigation, I don't think you'll be able to get more answers.
In an iOS app, to move between screens, I can either present a new ViewController ("move forward") or dismiss a current ViewController ("move backward").
In my naive understanding, this is simply a way of moving back and forth in the stack of ViewControllers kept by the app.
I have an intuitive preference for dismissing a ViewController (where possible) rather than presenting a new ViewController. It gives me the feeling of operating within a finite set of ViewControllers which in turn makes me feel the app is memory efficient.
Say I am on View A and want to show View B, then presenting A would result in a stack A-B-A whereas dismissing B would keep the stack at A.
My question is this: is that justified? is there any (programmatic) downside to perpetually working by presenting new ViewController? Is it memory inefficient?
I wonder how many previous Views are being saved by the app and how long the stack could get, and if that is a reason to dismiss whenever possible.
I am not 100% sure if i understood you correctly, but it seems in-efficient memory wise and not sure how possible it is. You want to keep going backward basically, just dismissing views, right?
Meaning when you load B, then A, then you want to dimiss A to go to B again? Am i understanding this ok?
In that case views would have to be in a constant "stack" and i am sure it will make things go slower, but more imporatantly, from user perspective and user experience, something that they are not used to at all :/
My understanding would be: If you want to show A again, then you should be going back to the instance of screen A that you already have. There might be special cases where it makes sense to have multiple instances of A (like detail screens for different objects), but even then: What you definitely should not do is build a never-ending stack of view controllers because as you already assumed: This will consume lots of unnecessary memory.
Maybe you should take a look at existing apps or the Apple Human Interface Guidelines and try to understand better how their view hierarchy works.
My app consists of two views. The first one is a GMSMapView and the second one is used to connect to a Bluetooth device that sends coordinates.
After the Bluetooth device is connected, I use a delegate to send the information back to the map view and move a marker around. To transition between views I was previously using segues, this didn't stop the Bluetooth view controller and the data made its way like I wished to the map view.
I ran into the problem of my map view being reinitiated so I decided to use a navigation controller. Now I use a push segue to get to my second view, and pop to come back to the same instance of the first one. Great, that worked! The issue I have now is that popping the second view seems to stop it completely from running in the background like it used to. Is there a way to keep it running in the background like it did before?
What I'm currently using to pop the second view is
self.navigationController?.popViewControllerAnimated(true)
Any idea would be appreciated! Thanks!
A popped view controller does not "stop running". It is returned to you, and if you don't retain it, it is completely destroyed.
If you don't want that to happen, retain it when it is returned. You are currently ignoring the returned view controller:
self.navigationController?.popViewControllerAnimated(true)
Instead, keep a reference to it:
self.mySecondViewController =
self.navigationController?.popViewControllerAnimated(true)
Be warned, however, that this is a very unusual architecture. You will not be able to use the storyboard segue to push again, because it will push a different copy. It would be better to abandon your navigation controller architecture entirely, as it is completely unsuited to the idea of a view controller persisting after it is popped. If you want an architecture where two view controllers persist simultaneously, you would be better off using a UITabBarController — or, even better, reorganize your app completely. The notion that you need the view controller to persist after being popped is a "bad smell": it means that you have put the functionality in the wrong place. Put the functionality in a place that does persist, rather than forcing the view controller to persist in some artificial way.
I got some serious problems understanding the viewcontroller stack.
When will my app use a stack to save the previous viewcontrollers? Only if I use a navigation viewcontroller or anytime I use normal viewcontrollers and segue modally between them?
So I was just wondering if I use some sort of chained routine for example, like going from vc 1 to vc 2 and from vc 2 back to vc 1. No navigation controller, just modal segues, no unwinding.
Does my app got performance issues because of a stack (which will grow everytime I go around) or doesn't it make any difference?
----updated
So basicly this is my problem. If I went through the routine of the app, the views get stacked everytime I do a transtition.
UINavigationController will retain any controller you push onto it's navigation stack until you pop it back off.
Any UIViewController will retain a controller it presents modally until that child controller is dismissed.
In either case every controller will at a minimum consume some memory until you remove it. Apps which construct ever expanding stacks of controllers are likely to encounter a number of issues including:
you will eventually run out of memory, how fast depends on how much memory each controller uses.
you may see unexpected side effects if many controllers in the background react to the same event.
users may become confused if they change state in an instance of controller 'A', push an instance of controller 'B' on top of it, and then "return" to a second instance of 'A' added to the top of the state. Since they're looking at a new controller and view whatever selection, scroll position, user input, or other state they set on the previous instance may be lost.
developers, including you, may come to dread touching this app.
I suspect that everyone will have a better experience if you view controller management matches whatever visual metaphor you are presenting to the user.
The Problem
I'm currently building an iPad game using SpriteKit. The gameplay is driven by sound provided by EZAudio. After running the Instrumentation tools to profile my app, I noticed that whenever the GameViewController is shown memory allocation jumps up. When I repeatedly show the screen (5+ times) it crashes the app. My project uses ARC.
Navigation
The navigation consists of 4 ViewControllers:
MenuViewController: This shows the menu
CharacterSelectionViewController: It lets you a pick a character to use
GameViewController: This lets you play a game with the player chosen
ScoreViewController: It shows you the score you achieved in the game
1 - MenuViewController
From here you can navigate to CharacterSelectionViewController via a Show (e.g. Push) segue.
2 - CharacterSelectionViewController
You can navigate to GameViewController via a Show (e.g. Push) segue. There is also a back button that goes back to MenuViewController with the following code:
[self.navigationController popViewControllerAnimated:YES];
3 - GameViewController
It first shows a 5 second countdown (using NSTimer)
The game starts with the character chosen in CharacterSelectionViewController
The game can be paused, allowing you to quit and go back to MenuViewController via a manual Show Detail (e.g. Replace) segue.
When the game ends, a manual Show (e.g. Push) segue is called that navigates to the ScoreViewController.
It's view hierarchy consists of three sets of view - one for the countdown, one for the pause menu and one for the game. These are subsequently shown/hidden. See the view hierarchy below:
4 - ScoreViewController
This allows you to quit or restart the game. When quit it pressed it performs a Show Detail (e.g. Replace) segue to MenuViewController. If restart is pressed it performs an unwind to CharacterSelectionViewController.
Responses
Please provide answers regarding:
How this kind of leak could occur
Any observations you have from the allocation and leaks screenshots
Allocation
Below, you can see the increasing allocations as I cycle through the apps screens to repeatedly show the GameViewController. I used Mark Generation to be able to show the increase in memory allocations.
Leaks
Here you can see the memory leak that occurred. This is ordered by size.
Ignore the leaks for the moment; fix the generational accretion first.
Is that generation snapshot representative of what is left after a typical snapshot? Typically, you'd want to show view controller, take snapshot, hide then show view controller, take snapshot, etc... as many times as you can without crashing (or 10 times if it doesn't crash).
Then look at generation 3 or 4 as that'll be the most stable representation of per-generation accretion.
If it is representative, it looks like you are leaking everything that the view controller would normally allocate. Ultimately, you are looking for the "root" of your object graph that is keeping everything around. Fix the reason why the root is sticking around and the rest'll likely go away.
I wrote a weblog post about this. It is a bit outdated, but the analysis workflow remains the same.
http://www.friday.com/bbum/2010/10/17/when-is-a-leak-not-a-leak-using-heapshot-analysis-to-find-undesirable-memory-growth/
How are you unwinding from your various view controllers? I note that you mention that when the game ends you're pushing another VC onto the stack, but I presume this VC chain will at some point unwind back to your initial menu? (In essence, I wonder if you're just looping around, hence adding new VCs to the stack everytime you play a game.)
To create an un-wind segue, simply create an empty method in the destination VC (i.e.: your main menu) as such:
- (IBAction)unwindToMainMenu:(UIStoryboardSegue*)sender
{
// Intentional NOP
}
(N.B.: Make sure it's also listed in the header.)
You can then call this as you would any other segue in your storyboard by dragging from the source object in question to the exit option at the top of the VC that contains the source object in the storyboard. This will present you with a list of segues to choose from. (You can verify that the segue is setup correctly by selecting the source object in the storyboard - the Connections inspector should list the unwind segue within the Triggered Segues section.)