Search code examples
swiftinheritancesprite-kitprotocols

Understanding how to turn Classes into Protocols in Swift


My code works fine! But, the more I study about Abstract classes and Protocols the more I start to think that the "logic" behind my code is totally wrong. Let me explain you more detailed:

I have a class named SpaceObject which is a kind of abstraction for the inherited classes. It basically hold some properties and physics. This class should not be initialized since it works just as "blueprint"

class SpaceObject: SKSpriteNode {

    final var atmosphere: SKSpriteNode

    init(style: SKTexture, initialVelocity: CGVector) {
        atmosphere = SKSpriteNode(texture: SKTexture(imageNamed: "atmosphere"), size: CGSize(width: 100, height: 100))
        atmosphere.zPosition = 2

        // calls the initializer
        super.init(texture: style, color: .clear, size: style.size())

        physicsBody = SKPhysicsBody(circleOfRadius: style.size().width)
        physicsBody?.velocity = initialVelocity
        physicsBody?.affectedByGravity = false
        ...

        addChild(atmosphere)
    }

    final func cleanAtmosphere() {
        atmosphere.run( ... )
    }

}

Here I have the subclasses. They just "fill" the SpaceObject class but use its physics and some other functions (such as cleanAtmosphere()).

final class World: SpaceObject {

    enum WorldType: String {
         case Venus, Earth, ...
    }

    var type: WorldType
    var gravity: CGFloat

    init(world: WorldType, initialVelocity: CGVector, gravity: CGFloat) {
        self.type = world
        self.gravity = gravity

        // calls the initializer
        super.init(style: SKTexture(imageNamed: "world_\(self.type)"), initialVelocity: initialVelocity)
    }

    func touchedOccurred() {
        ...
        cleanAtmosphere()
        ...
    }

}
final class Star: SpaceObject {

    enum StarType: String {
         case Sirius, Polaris, ...
    }

    var type: StarType

    init(star: StarType, initialVelocity: CGVector) {
        self.type = star

        // calls the initializer
        super.init(style: SKTexture(imageNamed: "star_\(self.type)"), initialVelocity: initialVelocity)
    }

    func explode() {
        ...
    }

}

What I do for creating an object is just:

let newWorld = World(world: .Venus, initialVelocity: someVector, gravity: CGFloat(8.87))

and I'm sure it will have an atmosphere SKSpriteNode and some physics rules which are valid for each SpaceObject.

But... I'm not sure I'm doing it correctly, I read that protocols need to handle situations like this, but I still can't figure out how. Any hint?


Solution

  • You can use inheritance to do what you want, but protocols are also an option. The advantages of protocols are that Swift structs can use them and a class can conform to multiple protocols but can inherit from only one class.

    You mentioned physics rules. If you wanted to use a protocol, you would create a protocol for the rules and include the functions that classes/structs have to implement to conform to the protocol. That function would create the rule.

    protocol PhysicsRules {
        // Add any arguments the function takes.    
        func yourPhysicsRule()
    
        // Add any other physics functions you want here.
    }
    

    Then your World and Star classes conform to the protocol and implement the yourPhysicsRule function.

    final class World: SpaceObject, PhysicsRules {
    
       func yourPhysicsRule() {
            // Add the code to create the rule here.
        }
    
    }
    

    You could come up with better names for the protocol and the function, but this gives you an idea of what you have to do to transition to a protocol.

    Doing a web search for Swift protocols provides many tutorials on protocol-oriented programming, including the following:

    A Beginner’s Guide to Protocols and Protocol Extensions in Swift

    Protocol-Oriented Programming Tutorial in Swift 5.1: Getting Started