Swift 傻瓜式 SpriteKit ARC

Swift SpriteKit ARC for dummies

我一直在努力思考强大的参考循环,但我很挣扎。我一直在阅读 apple 和某些网站上的文档,我觉得他们并没有真正解决我的问题。

我知道你必须使用 weak 和 unowned 取决于对象是否可以为 nil。所以说你必须像这样class 2

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

class Dog {
  weak var person: Person?
}

相互引用我知道其中一个必须使用 weak/unowned。这是大多数教程中看到的 classic 示例。

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

class Person {

 unowned let gameScene: GameScene

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

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

我还了解到协议也会导致内存泄漏,因此他们处理它们的方法是将其设为 class 协议

protocol TestDelegate: class { }

我试图引用协议的地方使它变弱 属性

 class: SomeClass {

  weak var myDelegate: TestDelegate?

 }

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

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

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

所以我的主要问题是

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

2) 在我简单的 classes 中还有哪些其他东西可能会产生强大的引用循环?

例如,我正在使用 class 创建平台

class Platform: SKSpriteNode {

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

现在我有类似的 class 陷阱、敌人等。同样,它们通常只是设置精灵属性(物理体等),并有一些方法来设置它们的动画或旋转它们。它们没有什么特别的,特别是在引用其他 classes 或场景时。

在我的 gameScene 中,我有一个创建平台的方法(对于敌人、陷阱等也是一样)

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 个被删除,不管我更改顺序还是 create/delete 某些平台。所以看起来他们正在创建强引用,除了 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 没有引用任何场景或自定义 class,它只是创建一个精灵并为其设置动画。

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

 class GameScene: SKScene {

   var flag: Flag!

  func didMoveToView... 

 }

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

weak var flag: Flag!

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

这样做有什么明显的遗漏吗? 在 Instruments 中找到它们有什么好技巧吗,因为这对我来说似乎很疯狂。 这让我感到困惑,主要是因为我的 classes 非常简单并且没有引用其他自定义 classes、场景、viewControllers 等

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

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

  1. 强储属性
  2. 强local/globalvariable/constant

With strong I mean it is not declared as weak or unowned.

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

在我简单的 classes 中还有哪些其他东西可能会产生强大的引用循环?

不用多说了。当对象 A 对对象 B 具有强引用时...对 A 具有强引用时,您就有了一个强引用循环。

几点建议:

跟踪取消初始化

只需向您的 class 添加一个反初始化器,并在控制台上检查您的对象是否在您期望的时候被反初始化

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

让 SpriteKit 管理强引用

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

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

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

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

我整天都在研究我的 spriteKit 项目,也在 playground 上做了很多试验,我的游戏现在完全没有泄漏,没有找到强大的参考循环。

实际上,我在仪器中看到的很多 leaks/persistant 类 只是因为其他东西没有释放。

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

这个问题对我有帮助

iOS 7 Sprite Kit freeing up memory

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

像这样的 "SKAction repeat forever" 显然会导致泄漏。

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 错误报告。