sheldor
sheldor

Reputation: 159

SpriteKit and SwiftUI Animation (Memory Leak - NSXPCConnection?)

I have a problem: In a bigger project with a similar structure I am getting a memory Leak from the Instrument Tool.

If you are putting this in Xcode and run, you should see a line which is moving to the right side and then to the left. When the Button is pressed, the Line jumps to a defined position. If you use the Memory Leak Tool there is an Error after the Button is tapped. I don't know why. Is this a bug? Do I have a basic mistake in the code? How to avoid this?

Is this the right way of connecting the animation calculation with SwiftUI, that I have the SKScene as an ObservableObject and mark the animation as @Published for doing this animation?

I appreciate any answers.

In the leakage Tool is a note, that the responsible frame is:

[NSXPCConnection remoteObjectProxyWithErrorHandler:]

Thanks for reading, here is the example code: Overview

Animation Calc

    import SwiftUI
    import SpriteKit

    struct MyAnimation{

        var lenght:CGFloat = 0  //Position of the line end
        var up: Bool = true  //if the line is moving to right or left

        mutating func change(){
        
            self.up ? (lenght += 1) : (lenght -= 1)
        
            if lenght > 100{
                up = false
            }else if lenght < 0{
                up = true
            }
        }
    }

SKScene for updating


    class GameScene: SKScene, ObservableObject{
    
        @Published var ani: MyAnimation  //handles the calculation
    
        override init(){
            ani = MyAnimation()
            super.init(size: CGSize(width: 200, height: 100))
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func update(_ currentTime: TimeInterval) {
            ani.change()  //new Position is calculated
        }
    }

Content View


    struct ContentView: View {
    
        @StateObject var game = GameScene()
    
        var body: some View {
            VStack{
                ZStack{
                    SpriteView(scene: game).opacity(0)
                    MyPath().environmentObject(game)
                }
                Start().environmentObject(game)  
                //Button to let the line jump to the defined position
        
            }
        }
    }

Path to animate


    struct MyPath: View{
        
        @EnvironmentObject var game: GameScene
        
        var body: some View{
            Path{ path in
                path.move(to: CGPoint(x: 50, y: 50))
                path.addLine(to: CGPoint(x: 200 + game.ani.lenght, y: 220))  
                //here is the length property of the MyAnimation struct and should cause the redraw

                path.closeSubpath()
            }
            .stroke(Color.black, lineWidth: 4)
        }
    }

Button


    struct Start: View {    //Button
        
        @EnvironmentObject var game: GameScene
        
        var body: some View {
            Button(action: {
                game.isPaused = true
                game.ani.lenght = 30
                game.isPaused = false
            }, label: {
                Text("Start")
            })
        }
    }

For Copy Paste

    import SwiftUI
    import SpriteKit

    struct MyAnimation{

        var lenght:CGFloat = 0  //Position of the line end
        var up: Bool = true  //if the line is moving to right or left

        mutating func change(){
        
            self.up ? (lenght += 1) : (lenght -= 1)
        
            if lenght > 100{
                up = false
            }else if lenght < 0{
                up = true
            }
        }
    }


    class GameScene: SKScene, ObservableObject{
    
        @Published var ani: MyAnimation  //handles the calculation
    
        override init(){
            ani = MyAnimation()
            super.init(size: CGSize(width: 200, height: 100))
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        override func update(_ currentTime: TimeInterval) {
            ani.change()  //new Position is calculated
        }
    }



    struct ContentView: View {
    
        @StateObject var game = GameScene()
    
        var body: some View {
            VStack{
                ZStack{
                    SpriteView(scene: game).opacity(0)
                    MyPath().environmentObject(game)
                }
                Start().environmentObject(game)  
                //Button to let the line jump to the defined position
        
            }
        }
    }


    struct MyPath: View{
        
        @EnvironmentObject var game: GameScene
        
        var body: some View{
            Path{ path in
                path.move(to: CGPoint(x: 50, y: 50))
                path.addLine(to: CGPoint(x: 200 + game.ani.lenght, y: 220))  
                //here is the length property of the MyAnimation struct and should cause the redraw

                path.closeSubpath()
            }
            .stroke(Color.black, lineWidth: 4)
        }
    }


    struct Start: View {    //Button
        
        @EnvironmentObject var game: GameScene
        
        var body: some View {
            Button(action: {
                game.isPaused = true
                game.ani.lenght = 30
                game.isPaused = false
            }, label: {
                Text("Start")
            })
        }
    }

Upvotes: 4

Views: 534

Answers (2)

dem.nz
dem.nz

Reputation: 36

I solved it by using NotificationCenter to update my skscene

NotificationCenter.default.addObserver(forName: "updateLevel", object: nil, queue: nil,
                                  using: { [weak self]_ in
                  self?.updateLevel()
              })

in swiftUI View:

Button(action: {
                   NotificationCenter.default.post(name: "updateLevel", object: nil, userInfo: ["useAI":false])
               }, label: {
                   Image("restart")
                       .resizable()
                       .frame(width: 110, height: 110)
                       
               })

Upvotes: 0

de.
de.

Reputation: 8527

I could reproduce the same leak with the following code:

  import SwiftUI

  struct ContentView: View {
    var body: some View {
        VStack{
            Start()
        }
    }
  }

  struct Start: View {
    var body: some View {
        Button(action: {
        }, label: {
            Text("Start")
        })
    }
  }

Without having more insights into this issue, I would assume that you are not responsible for the leak, but Apple is.

I think you have no choice but to ignore the leak for now if you still wish to use SwiftUI.
By the way, I had worse issues with SwiftUI, and I have abandoned it for now because I believe it just isn't ready yet.

Upvotes: 4

Related Questions