本文介绍了UINavigationViewController 中的重 SceneKit 场景需要很长时间来解除分配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用使用标准的 UINavigationViewController.

My app uses standard UINavigationViewController.

在某些时候,用户导航到了一个显示 SceneKit 场景的 ViewController.

At some point, user has navigated to a ViewController which shows a SceneKit Scene.

这个场景的内存非常大,因为它有大约 6000 个几何图形,每个几何图形都有自己的材质(这需要这样).

This scene is very heavy in memory, since it has around 6000 geometries and each geometry has its own Material (this needs to be this way).

当用户点击顶部栏的后退按钮以返回上一个视图控制器时,应用程序正确弹出当前视图控制器并显示下方.

When user clicks the back button at the top bar in order to return to the previous viewController, the app correctly pops the current View Controller and shows the one below.

然而,在大约 4 秒内,显示的新 ViewController 的 UI 冻结.

However, for around 4 seconds, the UI of the new ViewController shown freezes.

我使用了 Instruments Time Profiler,我可以看到这 4 秒中有很大一部分是由这些 SceneKit 方法占用的:

I have used Instruments Time Profiler, and I can see that big part of those 4 seconds are taken by these SceneKit methods:

-[NSConcreteMapTable countByEnumatingWithState:objects:count:]

-[NSConcreteMapTable countByEnumeratingWithState:objects:count:]

-[SCNMetalResourceManager _geometryWillDie:]

-[SCNMetalResourceManager _geometryWillDie:]

-[SCNMetalResourceManager _materialWillDie:]

-[SCNMetalResourceManager _materialWillDie:]

这是有道理的,因为有很多几何形状和材料.

It makes sense because there are so many geometries and materials.

我该如何解决这种情况,以便在释放重场景时 UI 不会被阻塞,无论是从 SceneKit 的角度(使释放更快)还是从 UINavigationViewController 的角度(可能强制释放场景发生在单独的线程?).

How can I solve this situation, so that UI is not blocked while the heavy Scene is being released, either from a SceneKit perspective (making deallocation quicker) or from a UINavigationViewController perspective (maybe forcing deallocation of the Scene to happen in a separate thread?).

推荐答案

我的理解是 SceneKit 使用它自己的后台线程来做工作,所以它不应该阻塞主线程.

My understanding is that SceneKit uses it's own background thread to do work, so it shouldn't be blocking the main thread.

主线程的阻塞可能与 UINavigationController 将内容动画化有关.我在尝试为 SCNViews/SCNScenes 设置动画时遇到问题,看起来 SceneKit 和 CoreAnimation 不能很好地协作.

It is possible that the blocking of the main thread has something to do with UINavigationController animating the content away. I've had issues when trying to animate SCNViews/SCNScenes and it looks like SceneKit and CoreAnimation do not collaborate very well.

我这里没有完整的上下文,但我可以建议您测试几件事:

I don't have the full context here, but I can suggest couple of things for you to test:

  1. 您可以尝试将带有重场景的 SCNView 的场景属性 scnView.scene = [SCNScene scene]; 设置为刚好在带有场景的 View Controller 之前的一个新的空场景弹出,以便动画不涉及重场景,SceneKit 会在其背景威胁中正确释放它.

  1. You can try setting the scene propery of the SCNView with the heavy scene scnView.scene = [SCNScene scene]; to a new empty scene just before the View Controller with the scene is popped off, so that the heavy scene is not involved in the animation, and SceneKit properly deallocates it in it's background threat.

您可以尝试在视图控制器中保持对重场景的强引用准备带有重场景的有问题的视图控制器.

You can try keeping a strong reference to the heavy scene in the View Controller that prepares the problematic View Controller with the heavy scene.

然后当后者被弹出并返回到它下面的那个时,弹出的视图控制器及其所有内容都应该被释放,除了沉重的场景,你将有一个对它的引用.

Then when the latter is popped off and you return to the one below it, the popped off View Controller and all of its content should have deallocated, except the heavy scene and you'll have a reference to it.

在这里您可以尝试设置为 nil(在动画完成后),它可能会在 SceneKit 后台线程上正确释放.

Here you can try setting to nil (after the animation has completed) and possibly it will deallocate properly on SceneKit background thread.

如果没有,您可以先删除所有 heavyScene.rootNode 子节点、动作等,然后再释放.

If not, you can first remove all heavyScene.rootNode child nodes, actions, etc and then deallocate.

如果这仍然不起作用,您可以先尝试使用递归函数一个一个遍历所有节点,然后先将它们的几何体/材质置零,然后再释放场景本身.

If this still does not work, you can first try using a recursive function to traverse all your nodes one by one and nil their geometry/material first and then deallocate the scene itself.

使用渲染循环 -renderer:updateAtTime: 方法在几帧(不到一秒)内移除节点.然后你可以确定 SceneKit 会按照它设计的方式处理它们.

Using the rendering loop -renderer:updateAtTime: method to remove the nodes over several frames (less than a second). Then you can be sure that SceneKit will deal with them the way it is designed to.

您必须检测用户何时按下 UINavigationController 的后退按钮",并且带有重场景的 VC 被弹出.然后结束,比如说 20 帧(60fps 的 1/3 秒),在每帧移除 5% 的节点,所以仍然会有短暂的延迟,但 1/3 秒,希望不会引起注意.

You will have to detect when the user presses UINavigationController "Back button" and the VC with the heavy scene is being popped off. Then over, say 20 frames (1/3 of a second at 60fps), remove 5% of nodes at each frame, so there will still be a short delay but 1/3 of a second and hopefully unnoticeable.

可以通过覆盖其 didMoveToParentViewController: 方法并检查 nil 是否作为 parent 参数传递来检测被弹出的 VC.

The detection of the VC being popped off can happen by overriding its didMoveToParentViewController: method and checking if nil is passed as the parent parameter.

例如

- (void)didMoveToParentViewController:(UIViewController *)parent {

    if (!parent) {
        // work that should be done when VC is being removed from parent
        // maybe set a counter in the VC being popped off to
        // count down from 20 to 0, -1 at each -update call
    }
}

这篇关于UINavigationViewController 中的重 SceneKit 场景需要很长时间来解除分配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

11-02 10:20