【问题标题】:Swift SpriteKit ARC for dummies用于假人的 Swift SpriteKit ARC
【发布时间】:2016-06-18 07:58:57
【问题描述】:

我一直在努力思考强大的参考周期,但我正在苦苦挣扎。我一直在阅读来自苹果和一些网站的文档,我觉得它们并没有真正解决我的问题。

我知道你必须使用 weak 和 unowned ,这取决于对象是否可以为 nil。所以说你必须有2个这样的课程

class Person {
   var dog: Dog?
  ....
}

class Dog {
  weak var person: Person?
}

相互引用我知道其中一个必须使用弱/无主。这是大多数教程中看到的经典示例。

我也见过这样的例子,它们对我来说也很有意义

class Person {

 unowned let gameScene: GameScene

 init(scene: GameScene) {
   self.gameScene = scene 
  ....
 }

我也明白 NSTimers 如果不失效会导致强引用循环。我没有使用 NSTimers,所以这应该不是问题。

我进一步了解协议也会导致内存泄漏,因此他们处理它们的方法是使其成为类协议

protocol TestDelegate: class { }

我试图引用协议的地方使它成为一个弱属性

 class: SomeClass {

  weak var myDelegate: TestDelegate?

 }

最后我知道了像这样捕获自我的闭包

SKAction.runBlock { [unowned self] in
   self.player.runAction....
}

然而,当谈到我实际的 spritekit 游戏时,我似乎到处都有引用循环,即使是简单的类,我也肯定不会引用另一个类,我不明白为什么。

所以我的主要问题是

1) 是所有属性都创建强引用循环,还是仅在类初始化之前创建全局属性?

2) 还有哪些其他因素可能会在我的简单类中创建强引用循环?

例如我正在使用一个创建平台的类

class Platform: SKSpriteNode {

 /// basic code to create platforms 
 /// some simple methods to rotate platforms, move them left or right with SKActions.

现在我为 Traps、Enemies 等提供了类似的类。同样,它们通常只是设置精灵属性(物理体等),并有一些方法来为它们设置动画或旋转它们。它们没有什么特别之处,尤其是在引用其他类或场景方面。

在我的游戏场景中,我有一种创建平台的方法(对于敌人、陷阱等都是一样的)

func createPlatform() {

 let platform1 = Platform(.....
 platformNode.addChild(platform1)

 let platform2 = Platform(....
 platformNode.addChild(platform2)

 let platform3 = Platform(...
 platformNode.addChild(platform3)

 // platform node is just a SKNode in the gameScene to help maintain the difference zPositions of my objects.

}

当我运行分配时,我可以看到 3 个平台中只有 1 个进入瞬态状态,当我切换到 menuScene 时,2 个保持持久状态。奇怪的是,总是只有 1 被删除,如果我更改顺序或创建/删除某些平台都没关系。所以看起来他们正在创建强引用,除了 1。所以如果我重播我的关卡几次,我可以很快在内存中拥有 50-100 个持久平台。因此,我的场景也没有得到 deinit,这会浪费更多的内存。

另一个例子是

class Flag {
  let post: SKSpriteNode
  let flag: SKSpriteNode

  init(postImage: String, flagImage: String) {

       post = SKSpriteNode(imageNamed: postImage)
       ...


       flag = SKSpriteNode(imageNamed: flagImage)
       ...
       post.addChild(flag)
  }
}

同样的问题。我在我的场景中创建了一些标志,有时一个标志不会被删除,有时会。同样,此类不引用任何场景或自定义类,它只是创建一个精灵并为其设置动画。

在我的场景中,我有一个用于标志的全局属性

 class GameScene: SKScene {

   var flag: Flag!

  func didMoveToView... 

 }

如果标志本身不引用 GameScene,为什么会创建一个强引用?我也不能用

weak var flag: Flag!

因为一旦标志被初始化,我就会崩溃。

执行此操作时我是否缺少明显的东西? 在 Instruments 中找到它们是否有一个好技巧,因为这对我来说似乎很疯狂。 它只是让我感到困惑,主要是因为我的类非常简单,并且没有引用其他自定义类、场景、viewControllers 等。

【问题讨论】:

  • 并非所有类都会导致引用循环,这仅取决于它们如何相互引用导致这种情况发生。在我正在进行的一个项目中,我遇到了未发布的代码。我所做的是为每个类添加一个 deinit { print("NameOfClass") },这样我就可以计算出什么被释放了,什么没有被释放。这很耗时。还要始终确保如果您使用任何委托,这些属性总是很弱。
  • 即使这是一个有趣的话题,你也应该一一提问。真的很难回答和涵盖你所有的问题,尤其是没有完全可行的例子来重现所描述的行为。您应该专注于一个特定问题(例如,为什么没有正确释放平台对象)就像其他人指出的那样,有一些方法/最佳实践可以遵循,当然您可以使用 Instruments (Leaks),但真正的代码仍然是需要一个准确的答案。

标签: swift reference sprite-kit instruments cycle


【解决方案1】:

是所有属性都创建强引用循环,还是只有在类初始化之前创建的全局属性?

对对象的每个强引用都可能成为强引用循环的一部分。可能是

  1. 强大的存储属性
  2. 强大的局部/全局变量/常量

strong 我的意思是它没有被声明为弱或无主。

事实上,如果在给定时刻存在从对象到对象本身的强引用路径,则创建循环并且 ARC 不会为循环中的所有对象释放内存。

还有哪些其他因素可能会在我的简单类中创建强引用循环?

没什么好说的了。当对象A 对对象B 具有强引用时,对A 具有强引用时,您就有了强引用循环。

一些建议:

跟踪取消初始化

只需向您的类添加一个取消初始化程序,然后在控制台上检查您的对象是否在您期望的时候被取消初始化

class Foo: SKSpriteNode {
    deinit {
        print(String(self))
    }
}

让 SpriteKit 管理强引用

避免场景图中节点之间的强引用。请改用SKNode 类提供的方法和计算属性来检索其他节点,例如。

.scene
.parent
.childNodeWithName(...)
.childrean

这种方法可能会导致一些性能问题,但您应该首先让您的代码正确,然后尝试提高性能。

【讨论】:

    【解决方案2】:

    感谢您的所有回答,尤其是 appzYourLift 和您的详细回复。

    我整天都在研究我的 spriteKit 项目,还对游乐场进行了很多试验,我的游戏现在完全没有泄漏,没有找到强大的参考周期。

    事实证明,我在仪器中看到的很多泄漏/持久类只是因为其他东西没有释放而存在。

    似乎永远重复的动作导致了我的泄漏。我所要做的就是遍历场景中的所有节点并删除它们的动作。

    这个问题帮助了我

    iOS 7 Sprite Kit freeing up memory

    更新:我最近再次访问了这个主题,因为我觉得不必手动删除节点上的操作,并且可能会变得很麻烦。经过更多研究,我找到了(我的)内存泄漏的确切问题。

    这样的“SKAction 永远重复”显然会导致泄漏。

    let action1 = SKAction.wait(forDuration: 2)
    let action2 = SKAction.run(someMethod) 
    let sequence = SKAction.sequence([action1, action2])
    run(SKAction.repeatForever(sequence))
    

    所以你需要把它改成这样才不会导致泄漏

     let action1 = SKAction.wait(forDuration: 2)
     let action2 = SKAction.run { [weak self] in
         self?.someMethod()
     } 
     let sequence = SKAction.sequence([action1, action2])
     run(SKAction.repeatForever(sequence))
    

    这直接来自我所做的 Apple 错误报告。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-14
      • 2013-06-05
      • 2015-03-30
      • 1970-01-01
      相关资源
      最近更新 更多