【发布时间】:2022-01-21 18:15:10
【问题描述】:
我试图了解 SpriteKit 场景的 frame cycle 何时在主 iOS 运行循环中运行。具体来说,我担心 AppDelegate 的 applicationDidBecomeActive(_:) 方法。我一直认为该方法是在应用程序激活后调用的,但在您呈现的场景的帧周期运行之前。
这对我正在构建的项目很重要,因为我使用applicationDidBecomeActive(_:) 方法来执行一些时间敏感的任务,例如检查时间戳、设置标志、启动计时器等。所以我需要可靠地预测这种方法何时会在帧周期内被调用(我们就称之为“游戏循环”)。
我做了一些测试,结果表明游戏循环在与 applicationDidBecomeActive(_:) 方法相关的不同时间运行,具体取决于应用运行的 iOS 版本。这是一个问题,因为这意味着我不能依赖此方法的单个实现来在正确的时间执行我需要的任务。
我想明确知道何时调用与 SpriteKit 游戏循环相关的 applicationDidBecomeActive(_:)。这似乎是任何编写 SpriteKit 游戏的人都需要了解的基本知识。我很震惊地看到它似乎因操作系统版本而异。我可能在测试和假设中犯了错误。但我会报告我在这里的发现,看看是否有其他人注意到这一点,以及是否有人能解释这种奇怪的行为。
在我当前的项目中,我一直在运行 iOS 12.4 的物理 iPhone 上进行测试,有时还对运行 iOS 13 的 iPhone 使用模拟器。通过使用 print 语句,我观察到 AppDelegate 的 @987654328 @ 方法和 SKScene 的 update(_:) 方法的调用顺序不同,具体取决于使用的 iOS 版本。
注意我的项目使用UIViewController的viewDidLoad()方法来呈现场景。我尝试改用viewWillLayoutSubviews(),希望这样可以更可靠地工作。但事实证明这更不可靠,所以我不会在这里讨论。
方法调用顺序(iOS 12.4):
didFinishLaunchingWithOptions
viewDidLoad
didMove
update
applicationDidBecomeActive
update
...
方法调用顺序(iOS 13):
didFinishLaunchingWithOptions
viewDidLoad
didMove
?
applicationDidBecomeActive
update
...
您可以看到两个版本的操作系统首先调用AppDelegate 的application(_:didFinishLaunchingWithOptions:) 方法,然后加载视图。在viewDidLoad() 中,我打电话让视图呈现我的SKScene。不出所料,场景的didMove(to:) 方法在视图呈现场景后被调用。但接下来发生的是奇怪的部分。
在 iOS 12.4 中,场景的 update(_:) 方法被调用,这表明场景执行了其游戏循环的单次运行。 然后AppDelegate 调用它的applicationDidBecomeActive(_:) 方法。接下来,update(_:) 方法再次运行。然后update(_:) 不断被调用,因为场景的游戏循环每秒触发 60 次,正如预期的那样。
在 iOS 13 中,update(_:) 方法不会在 didMove(to:) 被调用后立即被调用。相反,applicationDidBecomeActive(_:) 在didMove(to:) 之后被调用。只有这样update(_:) 方法才会运行(然后按预期继续运行)。
所以基本上,这里的问题是,在 iOS 12.4 中,游戏循环似乎在它出现后立即运行一次,在调用 applicationDidBecomeActive(_:) 之前。但在 iOS 13 中,这不会发生。
在调用applicationDidBecomeActive(_:) 之前,iOS 12.4 中的游戏循环会多运行一次,这是一个问题。这使得游戏的生命周期在不同版本的操作系统之间不一致,这意味着我将不得不编写不同的代码来处理不同操作系统版本的情况。要么,要么我必须重新设计依赖applicationDidBecomeActive(_:) 的应用程序部分,以找到更一致的处理方式。这也让我怀疑游戏循环的额外运行是否是 iOS 12 中的一个错误。
我一直认为应用程序的生命周期在操作系统版本之间是一致的(至少在 AppDelegate 和 SKScene 的方法调用顺序方面)。但这一发现使所有这些都受到质疑。我还没有测试过其他版本的 iOS,因为即使这是所有操作系统版本之间的唯一差异,它仍然意味着您的代码必须根据操作系统版本以不同方式处理事情。
在这个分析中再添一个皱纹......
我还创建了一个新的 SpriteKit 模板项目并执行了相同的测试。我发现了同样的差异,但有一个额外的特点:在 iOS 12.4 中,update(_:) 方法在didMove(to:) 之后被调用两次,在applicationDidBecomeActive(_:) 被调用之前。在 iOS 13 中,行为与上述相同。
我不确定为什么 update(_:) 会触发两次,而不是像在我的其他项目中那样触发一次。这似乎很奇怪。但是在“干净”模板项目中的这个测试表明这是一个真正的问题,而不是我自己的代码中的一些错误。
重申我的问题...
我想知道是否有其他人注意到这一点。也许我的结论是错误的。如果这是一个真正的问题,我想知道是否有任何“修复”可以使游戏循环在所有操作系统版本中以一致的方式工作。如果没有,任何人都可以提出一个好的解决方法,以便您在applicationDidBecomeActive(_:) 中的代码在游戏循环首次触发之前始终运行?我已经有了一些想法。但首先,我想确认这是 iOS 的实际问题还是我自己的代码中的错误。
【问题讨论】:
-
越想越觉得这一定是iOS的bug。我刚刚在 iOS 9.3 模拟器中运行了我的项目,没有额外的
update调用。我怀疑这是一个仅限于特定 iOS 版本的错误。所以我认为我们只需要解决它。 -
这可能是一个愚蠢的问题,但大多数 SpriteKit 应用程序都在
didMove(to:中完成所有设置。你有什么理由不能使用它吗? -
@SteveIves 这适用于应用程序在终止后启动的情况,但不适用于应用程序只是处于非活动状态然后再次变为活动状态的情况。每当用户在一段时间不活动后返回应用程序时,我的游戏需要做一些工作。
didMove(to:)不会在应用程序从后台返回、在跳板中、被电话中断等返回时调用。
标签: ios swift sprite-kit game-loop ios-lifecycle