Jonas0000
Jonas0000

Reputation: 1113

swift: Listen for "struct variable"-change event?

I got a small problem :

How is it possible to listen for the change of a struct-instance variable declared in another ("uneditable!!") class?


I added some small code snippets to maybe clarify my thoughts.

instructions:


code:

let fixedClass: FixedClass = FixedClass()
class FixedClass {
    struct MyObject {
        var abc = 0
    }
    var instance = MyObject()
    public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil,  repeats: true) }

    @objc func updateVar() {
        instance.abc+=1
    }
    func getVar() -> Int {
        return instance.abc
    }
}


let editableClass: EditableClass = EditableClass()
class EditableClass {
    public init() { }
    func listenForChange() {
        // listen for change event of FixedClass.getVar()
        print("Variable: \(fixedClass.getVar())")
    }
}


Calling with: editableClass.listenForChange()


To sum that up I'd like to listen for the change of the FixedClass.getVar() result - preferably avoid using loops or timers. However the most important thing is to get it working at least.

Any help would be very appreciated, thanks!

Upvotes: 3

Views: 2331

Answers (3)

Rob
Rob

Reputation: 437622

A couple of things:

  1. If the original class was Objective-C or otherwise participated in KVO (e.g. Swift dynamic properties of NSObject subclass, etc.) then you could observe changes. But but that's a fairly narrow use case. But that's a general pattern for making one's properties observable by other objects. For more information, see the Key-Value Observing Programming Guide.

  2. If you can’t edit the class, in some narrow cases you could theoretically subclass it and add whatever observation system you want. That obviously only works if you’re manually instantiating FixedClass and it is contingent upon how FixedClass was implemented, but in some narrow cases, you can achieve what you need via subclassing.

    You asked:

    Would you be so kind an share some code snippets with us?

    Sure, consider your FixedClass:

    class FixedClass {
        struct MyObject {
            var abc = 0
        }
        var instance = MyObject()
        public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil,  repeats: true) }
    
        @objc func updateVar() {
            instance.abc+=1
        }
        func getVar() -> Int {
            return instance.abc
        }
    }
    

    You could then define a subclass:

    class FixedClassSubclass: FixedClass {
        static let changeNotification = NSNotification.Name(rawValue: Bundle.main.bundleIdentifier! + ".FixedClassSubclassNotification")
    
        override func updateVar() {
            super.updateVar()
            NotificationCenter.default.post(name: FixedClassSubclass.changeNotification, object: self)
        }
    }
    

    Then you could do:

    let fixed = FixedClassSubclass()
    

    and

    NotificationCenter.default.addObserver(forName: FixedClassSubclass.changeNotification, object: nil, queue: nil) { _ in
        print("changed")
    }
    

    You can use whatever notification process you want. NotificationCenter. KVN. Delegate-protocol pattern. Whatever. The details of this will vary entirely based upon the details of FixedClass and you have given us a contrived example that is unlikely to be extensible in many situations.

I must confess to some general misgivings to the idea of trying to hook into the internal implementation details of a non-editable class. We generally strive for loosely coupled objects that only rely on published, supported interfaces. This endeavor violates both of those objectives. But I'll assume you have some good reason to do what you're attempting to do.

Upvotes: 1

Upholder Of Truth
Upholder Of Truth

Reputation: 4711

This is going to depend totally on how the 'real' FixedClass is defined. I.E. is it a subclass of NSObject, is it an ObjectiveC class, how the property you want to observe is defined.

As far as your actual example is concerned you could do it by subclassing like this:

var newFixedClass: NewFixedClass = NewFixedClass()
var editableClass: EditableClass = EditableClass()

protocol NewFixedClassProtocol: class {
    func changed(newFixedClass: NewFixedClass)
}

class NewFixedClass: FixedClass {
    public weak var delegate: NewFixedClassProtocol?

    override var instance: FixedClass.MyObject {
        didSet {
            self.delegate?.changed(newFixedClass: self)
        }
    }
}

class EditableClass: NewFixedClassProtocol {
    public init() {
        newFixedClass.delegate = self
    }

    func changed(newFixedClass: NewFixedClass) {
        print ("Value = \(newFixedClass.instance.abc)")
    }
}

So you basically create a protocol which the class doing the observing supports, create a subclass of the FixedClass which has a delegate of the protocol type and overrides the property of the FixedClass you want to observe with a didSet observer which then calls the delegate method. At some point you have to assign the class observing as the delegate of the sub class (I did it in the init method as a test).

So doing that I can observe when the structure changes but I haven't touched the FixedClass.

Note however that this method relies heavily on knowing about the original FixedClass so may not work for your 'real world' case.

(Also as an aside I couldn't get it to work with the globally defined instances of the classes and had to set them inside my initial view controller but that could be to do with how I was testing and doesn't alter the method involved)

Upvotes: 1

creeperspeak
creeperspeak

Reputation: 5523

One way to do this would be to use NotificationCenter to broadcast the change, and have your EditableClass listen for that change and react to it. Your implementation could look something like this:

class FixedClass { //Class names should start with capital letters
  struct MyObject { //Struct names should also start with capital letters
    var abc = 0
  }
  var instance = myObject()
  public init() { Timer.scheduledTimer(timeInterval: 0.4, target: self, selector: #selector(self.updateVar), userInfo: nil,  repeats: true) }

  @objc func updateVar() {
    instance.abc+=1
    //Broadcast the change, and pass this FixedClass instance
    NotificationCenter.default.post(name: Notification.Name("fixedClassChanged"), object: self)
  }
  func getVar() -> Int {
    return instance.abc
  }
}

Then you could react to this broadcast in your EditableClass like so:

class EditableClass {
  public init() { }
  func listenForChange() {
    //Observe same notification being broadcast by the other class
    NotificationCenter.default.addObserver(self, selector: #selector(processChange(_:)), name: Notification.Name("fixedClassChanged"), object: nil)
  }

  @objc func processChange(_ sender: Notification) {
    //When the notification comes in, check to see if the object passed in was a FixedClass, and if so process whatever needs to be processed
    if let fixed = sender.object as? FixedClass {
        print("Class changed to \(fixed.getVar())")
    }
  }
}

Upvotes: -1

Related Questions