How to observe changes, and store previous values, of an inherited object's property?

355 Views Asked by At

Set Up:

I'm subclassing SKSpriteNode() to create a custom subclass called SubNode().

The SKSPriteNode() class has a property called physicsBody which stores a SKPhysicsBody() object. SKPhysicsBody has a property called 'velocity' that is a CGVector()

Goal:

In my subclass I'd like to have a variable which always keeps the previous CGVector(). Meaning as soon as I call subNode.physicsBody?.velocity = CGVector(dx: 10, dy: 10) for example. subNode.previousVelocity should be set to the previous subNode.physicsBody?.velocity value.

Issue

If velocity would just be an inherited property of the superclass I could just override the property and attach an didSet observer. But since velocity is a property of SKPhysicsBody() and the SKPhysicsBody() object doesn't change when one of its properties changes, didSet doesn't get triggered and I'm unable to listen to its changes.

Please ask for more clarification if my explanation is unclear.

My subclass:

class SubNode: SKSpriteNode {
    
    var previousVelocity: CGVector = .zero

    // This doesn't get triggered because the physiceBody object doesn't change when one of its properties changes.
    override var physicsBody: SKPhysicsBody? {
        didSet {
            previousVelocity = oldValue?.velocity
        }
    }
    
    // If velocity would be a property of the superclass, this would trigger.
    override var velocity: CGVector {
        didSet {
            previousVelocity = oldValue
        }
    }
}

Creating the object subNode from the SubNode() subclass, setting the physics body and then the velocity"

let subNode = SubNode()
subNode.physicsBody = SKPhysicsBody()
subNode.physicsBody?.velocity = CGVector(dx: 10, dy: 10)
subNode.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
//should be true: subnode.previousVelocity == CGVector(dx: 10, dy: 10)
1

There are 1 best solutions below

0
Marco Boerner On

This is only a partial answer! Only an attempt to find a solution to the issue.

I've tried to somehow find a workaround. I noticed that as soon as the property of an object changes the getter of the object is triggered before. I created the following playground file and it seems to work. As soon as I change the text of the object, the previous name is stored in another variable. But from my testing this seems to work. I'm not sure if my logic is correct or if I'm missing something, it might also be a bit too much to achieve something that should be simpler.

Below I added the code for my subclass and from what I could test so far it actually works. However I'd appreciate some feedback as in where there could be pitfalls to this solution.

import UIKit

class ObjectClass {
    var name: String = "Initial Name"
}

class SuperClass {
    var object: ObjectClass?
}

class SubClass: SuperClass {
    
    var _oldObjectName: [String?] = [nil]
    
    var oldObjectName: String? {
        _ = object?.name
        return _oldObjectName.first ?? nil
    }
    
    override var object: ObjectClass? {
        get {
            // Is also being called before the object has been written to.
            if !_oldObjectName.contains(super.object?.name) {
                _oldObjectName.append(super.object?.name)
                _oldObjectName = _oldObjectName.suffix(2)
            }
            return super.object
        }
        set {
            super.object = newValue
        }
    }
}

let subClass = SubClass()

subClass.object = ObjectClass()

Using the above solution with my subclass:

class SubNode: SKSpriteNode {
    
    private var _previousVelocity: [CGVector?] = []
    var previousVelocity: CGVector {
        _ = physicsBody?.velocity
        return (_previousVelocity.first ?? CGVector.zero)!
    }
    
    override var physicsBody: SKPhysicsBody? {
        get {
            if !_previousVelocity.contains(super.physicsBody?.velocity) {
                _previousVelocity.append(super.physicsBody?.velocity)
                _previousVelocity = _previousVelocity.suffix(2)
            }
            return super.physicsBody
        }
        set {
            super.physicsBody = newValue
        }
    }
}

From my initial testing this seems to work so far:

let subNode = SubNode()
subNode.physicsBody = SKPhysicsBody()
subNode.physicsBody?.velocity = CGVector(dx: 10, dy: 10)
subNode.physicsBody?.velocity = CGVector(dx: 0, dy: 0)
//true: subnode.previousVelocity == CGVector(dx: 10, dy: 10)

Update:

One issue with this solution I have found is, that when the object from ObjectClass() is created as its own variable first and then changed through the variable directly, the getter of the subclass will not get triggered until it is accessed through the subclass. It's not an issue in my case but definitely not ideal.

let subClass = SubClass()

let object = ObjectClass()

subClass.object = object

// This won't trigger the getter obviously since the subclass is not accessed.
object.name = "New Name"
object.name = "Another Name"