crashoverride777
crashoverride777

Reputation: 10674

Swift SpriteKit use struct instead of class to render sprites

I have been updating my game recently to use more value types. I am still not 100% confident with weak and unowned in some cases so I went the struct way to avoid strong reference cycles. As per apples newer keynotes it seems value types are they way to go for the most part anyway.

I have never seen an example where structs are used to render sprites in a spriteKit game so I wonder what the drawbacks are. I understand that they are copied and not referenced but for my usage it seems to work.

So basically is there something I need to watch out for when doing this

struct Flag {
   let post: SKSpriteNode
   let flag: SKSpriteNode

  init(postImage: String, flagImage: String) {
     post = SKSpriteNode(imageNamed: postImage)
     // other set ups for post sprite

     flag = SKSpriteNode(imageNamed: flagImage)
     // other set ups for flag sprite
     post.addChild(flag)
   }

   func animate() {
       // code to animate flag
   }
}

Than in my SKScenes I simply add them as usual

 let flag = Flag(postImage: "FlagPostImage", flagImage: "FlagImage")
 flag.post.position = ...
 addChild(flag.post)
 flag.animate()

Now even if I create multiple flags in the same scene I seem to have no problems with this way. I am just curious because I have never really seen an example like this so I wonder if I am missing something, like performance drawbacks etc.

Thanks for any help.

Upvotes: 2

Views: 465

Answers (1)

R Menke
R Menke

Reputation: 8391

Personally I avoid creating Structs that contain Classes. Because Structs copy, each and every copy that get's passed around your app will increase the reference count of the Classes. This makes it harder to manage them instead of easier.

It is also useful to take a look at how UIKit uses Structs. A UIView is an object but has many defining properties that are Structs. For example it's frame.

Drop the code below in a playground to see some effects of this behaviour. The protocol is just to get some meaningful feedback form the playground.

protocol IDLookable : CustomPlaygroundQuickLookable {

    var id : Int { get set }

}

extension IDLookable {

    func customPlaygroundQuickLook() -> PlaygroundQuickLook {
        return PlaygroundQuickLook.AttributedString(NSAttributedString(string: "\(self.dynamicType) with id : \(self.id)"))
    }
}


class MyClass : IDLookable {

    var id : Int = 0

    init(id : Int) {
        self.id = id
    }
}

struct MyContainerStruct : IDLookable {

    var id : Int = 0
    var object : MyClass

    init(id : Int, object:MyClass) {
        self.id = id
        self.object = object
    }
}

class Scope {

    // ref count = 1
    var object = MyClass(id: 11)
    var structContainer : MyContainerStruct

    init() {
        // ref count = 2
        structContainer = MyContainerStruct(id: 222, object: object)
        messWithAClassInAStruct()
    }

    func messWithAClassInAStruct() {

        // ref count = 3
        var structContainerTwo = structContainer
        structContainerTwo.id = 333
        structContainerTwo.object // 11

        // altering the object in one struct will obvously update all references
        structContainerTwo.object.id = 1
        structContainer.object // 1
        structContainerTwo.object // 1

    }
}

let test = Scope()

One pattern that does make it easy to work with Reference Types in Value Types is to store them as weak optionals in the Value Types. This means that something will need to have a strong reference but chances are that some Class will be responsible for creating the Structs this is a good place to keep that strong reference.

struct MyContainerStruct : IDLookable {

    var id : Int = 0
    weak var object : MyClass?

    init(id : Int, object:MyClass) {
        self.id = id
        self.object = object
    }
}

class Scope {

    // ref count = 1
    var object = MyClass(id: 11)
    var structContainer : MyContainerStruct

    init() {
        // ref count = 1
        structContainer = MyContainerStruct(id: 222, object: object)
        messWithAClassInAStruct()
    }

    func messWithAClassInAStruct() {
        // ref count = 1
        var structContainerTwo = structContainer
        structContainerTwo.id = 333
        structContainerTwo.object // 11
    }
}

let test = Scope()

Upvotes: 2

Related Questions