When does the SpriteKit game loop run for the first time? - ios

I'm trying to understand when a SpriteKit scene's frame cycle runs within the main iOS run loop. Specifically, I am concerned about the AppDelegate's applicationDidBecomeActive(_:) method. I always thought that method was called after the app becomes active, but before your presented scene's frame cycle runs.
This is important to the project I am building because I use the applicationDidBecomeActive(_:) method to perform some time-sensitive tasks like examining a timestamp, setting flags, starting timers, etc. So I need to reliably anticipate when this method will be called during the frame cycle (let's just call it the "game loop").
I did some testing which suggests that the game loop runs at different times in relation to the applicationDidBecomeActive(_:) method, depending on which version of iOS the app is running on. This is a concern because it means I cannot rely on a single implementation of this method to perform the tasks I need at the correct time.
I want to know definitively when applicationDidBecomeActive(_:) is called in relation to the SpriteKit game loop. That seems like a fundamental thing that anyone writing a SpriteKit game needs to understand. And I'm shocked to see that it seems to vary depending on the OS version. It's possible that I made a mistake in my testing and assumptions. But I'll report what I found here and see if anyone else has noticed this, and if anyone can explain this odd behavior.
In my current project, I have been testing on my physical iPhone running iOS 12.4 and sometimes using the Simulator for an iPhone running iOS 13. By using print statements, I have observed that the AppDelegate's applicationDidBecomeActive(_:) method and the SKScene's update(_:) method are being called in a different order, depending on which version of iOS is used.
Note that my project uses UIViewController's viewDidLoad() method to present the scene. I tried using viewWillLayoutSubviews() instead, hoping that things might work more reliably that way. But that proved to be even less reliable, so I won't discuss that here.
Order of Method Calls (iOS 12.4):
didFinishLaunchingWithOptions
viewDidLoad
didMove
update
applicationDidBecomeActive
update
...
Order of Method Calls (iOS 13):
didFinishLaunchingWithOptions
viewDidLoad
didMove
?
applicationDidBecomeActive
update
...
You can see that both versions of the OS call AppDelegate's application(_:didFinishLaunchingWithOptions:) method first, then they load the view. In viewDidLoad(), I make my call to have the view present my SKScene. As expected, the scene's didMove(to:) method is called after the view presents the scene. But what happens next is the odd part.
In iOS 12.4, the scene's update(_:) method is called, which indicates that the scene performed a single run of its game loop. Then the AppDelegate calls its applicationDidBecomeActive(_:) method. Next, the update(_:) method runs again. Then update(_:) keeps being called over and over as the scene's game loop fires 60 times per second, as expected.
In iOS 13, the update(_:) method does not get called immediately after didMove(to:) is called. Instead, applicationDidBecomeActive(_:) is called right after didMove(to:). Only then does the update(_:) method run (and then continues running, as expected).
So basically, the issue here is that in iOS 12.4, the game loop appears to run once immediately after it is presented, before applicationDidBecomeActive(_:) is called. But in iOS 13 this does not happen.
It's a problem that the game loop in iOS 12.4 runs one extra time, before applicationDidBecomeActive(_:) is called. This makes the game's lifecycle inconsistent among different versions of the OS, and it means I will have to write different code to handle cases for different OS versions. Either that, or I must redesign the parts of the app that rely on applicationDidBecomeActive(_:) to find a more consistent way of handling them. It also makes me wonder if the extra run of the game loop is a bug in iOS 12.
I always assumed that the app's lifecycle was consistent between OS versions (at least regarding the order of method calls for AppDelegate and SKScene). But this discovery throws all of that into question. I have not yet tested with other versions of iOS, because even if this is the only discrepancy between all of the OS versions, it still means that your code must handle things differently depending on the OS version.
To add one more wrinkle to this analysis...
I also made a new SpriteKit template project and performed the same test. I found the same discrepancy, with one added peculiarity: in iOS 12.4, the update(_:) method is called twice immediately following didMove(to:), before applicationDidBecomeActive(_:) is called. In iOS 13, the behavior is the same as described above.
I'm not sure why update(_:) is firing twice rather than once as it does in my other project. That seems quite odd. But this test in a "clean" template project suggests that this is a real issue, rather than some error in my own code.
To reiterate my question...
I would like to know if anyone else has noticed this. Maybe I am mistaken in my conclusion. If this is a real issue, I wonder if there is any "fix" that can be done to make the game loop work in a consistent way for all OS versions. If not, can anyone suggest a good workaround so that your code in applicationDidBecomeActive(_:) consistently runs before the game loop first fires? I already have some ideas. But first, I want to confirm if this is an actual issue with iOS or just a mistake in my own code.

This isn't directly relevant but might give you a way forward...
SpriteKit tries to do automatic pausing and unpausing when the game goes into the background and back to the foreground. I had a situation where I wanted more control, and in particular did not want the game to always start up again on resume. My hack (which was working on iOS 12 and 13, though I don't recall versions) was to override isPaused in my derived game scene, e.g.,
override var isPaused: Bool {
get { super.isPaused }
set {
if forcePause && !newValue {
os_log("holding isPaused at true because forcePause is true", log: .app, type: .debug)
}
super.isPaused = newValue || forcePause
}
}
Then I'd have the additional variable forcePause to keep the game under control.
Perhaps you could do something similar, setting forcePause when you go into the background and then having applicationDidBecomeActive clear it. Perhaps that would be sufficient to keep iOS 12 from jumping the gun on you.

I used one of the code-level support incidents that I get as part of the Apple Developer Program to ask this question of one of their support engineers. What he told me was that there is no guarantee that update(_:) is always called after applicationDidBecomeActive(_:), so your app should not rely on such behavior. I had already implemented a workaround, and he told me I should either continue using that approach, or explore a different design that does not rely on the active state.
My workaround is:
Set a global Boolean variable called applicationDidBecomeActiveRan, initialized to false.
At the end of applicationDidBecomeActive(_:), set the global variable to true.
At the beginning of applicationWillResignActive(_:), set the global variable to false.
In the update(_:) method of the SKScene subclass, put this guard statement at the beginning of the method:
guard applicationDidBecomeActiveRan else { return }
This way, the rest of the code in my update(_:) method won't run unless applicationDidBecomeActive(_:) has already run. This ensures that the order of code execution for applicationDidBecomeActive(_:) and update(_:) will be consistent, no matter which iOS version the game is running on.
I was surprised to hear that the order of these two method calls is not guaranteed by default, because in every situation I have encountered up until this point, it has behaved in a consistent way (unless I just did not notice the inconsistency). Admittedly, I've never seen any Apple documentation that states that it should behave consistently. But when you see something happen a certain way every time, you naturally come to expect that it always works that way and indeed is supposed to work that way. It's helpful to learn that in rare edge cases like this one, it may not work in the usual way.

Related

Why does my iOS build behave differently after closing and restarting the app

We have a hybrid app that is a mix of Objective C and Swift that is showing some strange behavior in classes that haven't been touched in a long time. If we do a fresh build from Xcode the app works fine, but if we close the app and restart it, it behaves totally different. We re-attach the debugger and have seen viewWillAppear called but viewDidAppear is not called after re-opening. Other things like NotificationCenter Observers don't fire after closing and re-opening either. We've tried to track this down and I wish there was code that I could share that would point in the right direction, but it's a general thing in the app that is happening across most classes. When building for test flight it behaves the way that misses the viewDidAppeaer and the observers.

iOS Launch Screen Animation Time

It seems like the fade animation between the launch screen and my first view is really slow.
I don't think it used to be like that. Is there a way to control the speed of that transitional animation?
I looked at some apps on my phone and the launch screen doesn't fade as slowly as mine. What things could I have done to affect that?
(No I don't have slow animations turned on, only the fade animation is slow)
In WWDC 2012 video iOS App Performance: Responsiveness they enumerate a whole list of issues that have impact on the app startup time, ranging from attaching to unnecessary frameworks, optional linking to frameworks that you really could make required, the use of static initializers, overly complicated initial scenes, too much information stored in preferences, etc.
That video covers a range of topics, like the above, which impact startup time. In the demonstration, they show how one might benchmark the startup time. Unfortunately, in my experience, there's a good chance that you might not be able to do anything to fix this issue (e.g. you need certain features and therefore need certain frameworks), but it's still an illuminating video and it might give you some ideas of things you can try to alleviate the start-up performance issues.
If your app splash screen show more time, so please check following things in your app.
1. AppDelegate.m
in didFinishLaunchingWithOptions method have you called any heavy method which takes more time for finish task if yes then change that method location, basically in appDelegate class don't write any heavy methods.
2. first view of your app.
check viewDidLoad() method, if you call many method or any heavy method from here then your launch image will show still your control not come out from viewDidLoad method, so if you want call any methods at view launch time then call them from viewWillAppear or viewDidAppear method (in viewDidAppear method don't call any UI related methods)
I never figured out what was going on here, but the next day when I started up xCode and the simulator it was back to the normal loading time.

iOS 8 viewDidAppear called before appWillBecomeActive?

I'm just experiencing something weird and it seems to be a change in iOS 8.
Previously(iOS7) when testing appWillBecomeActive was called before viewDidAppear. Is it so that in iOS 8 it is the other way around? what would then be a good workaround in order to make my app work on both iOS versions? is there some variable to test if viewDidAppear was called so I could run my setup functions of the view again?
EDIT: it actually seems quite random in iOS8. sometimes viewDidAppear is called before appWillBecomeActive. Sometimes it's the other way around...
appWillBecomeActive is a delegate located in your Application Delegate itself.. there's no guarantee that it will be called before any other UIViewController delegates (viewWillAppear,DidLoad,Init)
if you want to make any logic before loading of any other pages come alive.. you may want to use application:didFinishLaunchingWithOptions: and you may want to load the launching view by yourself or create a new delegate to detect that you finished the logic that you'll put in your application:didFinishLaunchingWithOptions: .

Is there any scenario that can cause ViewDidLoad to be called before didBecomeActive?

I know it's sounds silly but just to clear a point.
Is there any chance that view did load will be called before didBecomeActive ?
Is it totally impossible ?
EDIT
We have a crash that happens when user is coming back to the app from the background and we start to use openGL. The crash error points that we try to use openGL in the background.
It is important to say that our app lives in the background as a VOIP app.
We try to figure out if there is a chance that somehow we are triggering something in the background thats causes the app restart openGl in the background.
In the stack we see:
[VideoCallViewController viewDidLoad] (VideoCallViewController.m:283)
And few lines after that:
[GPUImageContext createContext]
And finally:
gpus_ReturnNotPermittedKillClient + 10
We are trying to figure out if there is a way that [VideoCallViewController viewDidLoad] was called in the background or that we must assume that we are in the foreground, and somehow moving to the background right after the viewDidLoad ?
Second option
The second option is that we are indeed moving to the background right after the viewDidLoad. The point here is that we are listening to AppWillResignActive and we pause the GPUIMage. So we can not understand why do we get the crash ?
Thanks
Thanks
When / Where do you instantiate the various GPUImage objects that you are using? Is it within viewDidLoad or possibly within init:?
It's just pure rampant speculation here since you didn't really post any code...
but if you are disposing of objects when the app heads to the background that are not then re-created when it comes back to the foreground (perhaps because the viewController was retained by a parent, and therefore init: was not called again but viewDidLoad was...) Then you may be trying to send OpenGL messages to objects that don't actually exist anymore.
On the (probably unlikely) chance that my speculation is right, you could easily fix it with the common "getter" pattern of:
- (GPUImageObjectOfInterest*)instanceOfObject {
if (!_classVariableOfThisType) {
_classVariableOfThisType = [[GPUImageObjectOfInterest alloc] init];
// custom configuration, etc...
}
return _classVariableOfThisType;
}
and then use [self instanceOfObject]; wherever you used to use _classVariableOfThisType
It's a low overhead, but reasonably foolproof way of making sure a key object exists under a wide range of app interruption / background & foreground & low memory conditions.
Don't be shy to post too much code though, we can read through an entire class if needed. Some of us like reading code! (and it will really help the quality of response you get...)

viewWillLayoutSubviews getting called after applicationDidEnterBackground notification

I'm having an issue where my app is crashing on sleep, and sometimes on home. I'm getting a BAD_ACCESS error in a thread called gpus_ReturnNotPermittedKillClient, which tells me that my app is making UI changes in the background, which to my understanding is a no-go. Therefore, I'm stepping through my code to see what happens on home / sleep, and I find that my breakpoint in my VC's -viewWillLayoutSubviews method is getting hit AFTER the breakpoints in the -applicationWillResignActive and -appplicationDidEnterBackground notifications (in which I'm attempting to stop all updates from an asynchronous callback function).
That doesn't seem to make any sense. From the application's perspective, if it's not cool to do UI updates in the background, why call viewWillLayoutSubviews after you're in the background?
EDIT: It appears to do this even when my app doesn't crash. Could it just be lldb getting things out of order?
I think you simply need to be tolerant of this. Per this tech note, you can't do any GLES rendering in the background. My recommendation would be for your app to set a flag when applicationWillResignActive is called, and then before doing any rendering work you check that flag and don't do the work (and perhaps just call -setNeedsDisplay on the view so that if your app becomes active again it will know to draw that view). You seem troubled by the fact that viewWillLayoutSubviews is getting called "late", but I don't see how that really matters. (i.e. layout != rendering) I would be surprised if your view's -drawRect: method were getting called after applicationDidEnterBackground but I would still say that it would be your responsibility to check a flag and not render if your app is in the background.

Resources