8

I have a class with a static var where the current online connection status is stored. I want to observe the value of ConnectionManager.online through other classes. I wanted to do this with KVO, but declaring a static variable as dynamic causes an error:

class ConnectionManager: NSObject {
    dynamic static var online = false
    // adding 'dynamic' declaration causes error:
    // "A declaration cannot be both 'final' and 'dynamic'
}

What is a most elegant way of doing this?

Update. This my code for the KVO part:

override func viewDidLoad() {
    super.viewDidLoad()

    ConnectionManager.addObserver(
        self,
        forKeyPath: "online",
        options: NSKeyValueObservingOptions(),
        context: nil
    )
}

override func observeValueForKeyPath(keyPath: String?, 
                                     ofObject object: AnyObject?, 
                                     change: [String : AnyObject]?, 
                                     context: UnsafeMutablePointer<Void>) {
    if keyPath == "online" {
        print("online status changed to: \(ConnectionManager.online)")
        // doesn't get printed on value changes
    }
}
Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
MJQZ1347
  • 2,607
  • 7
  • 27
  • 49

4 Answers4

8

As for now, Swift cannot have observable class properties. (In fact, static properties are just global variables with its namespace confined in a class.)

If you want to use KVO, create a shared instance (singleton class) which has online property and add observer to the instance.

OOPer
  • 47,149
  • 6
  • 107
  • 142
5

I solved it with the singleton pattern suggested by @OOper.

class ConnectionManager: NSObject {
    static let sharedInstance = ConnectionManager()
    private override init() {} // This prevents others from using the default '()' initializer for this class.
    @objc dynamic var online = false
}

Then:

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.tableFooterView = UIView()

    ConnectionManager.sharedInstance.addObserver(self,
                         forKeyPath: "online",
                         options: [.new, .initial],
                         context: nil)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if object is ConnectionManager && keyPath == "online" {
        // ...
    }
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
MJQZ1347
  • 2,607
  • 7
  • 27
  • 49
  • added `@objc` per the errors here: https://stackoverflow.com/questions/46419893/nsunknownkeyexception-was-sent-to-an-object-that-is-not-kvc-compliant-for-the – pkamb Mar 29 '20 at 08:12
2

Try replacing dynamic static var online = false to @nonobjc static var online = false

What's happening is that because it inherits from NSObject, Swift is trying to generate getters and setters for it. Because you are creating it in swift, using the @nonobjc attribute solves the problem.

EDIT:

I don't believe you can observe static variables through KVO because of how it works

Here is a link and snippet from Apple's Guide on KVO

Unlike notifications that use NSNotificationCenter, there is no central object that provides change notification for all observers. Instead, notifications are sent directly to the observing objects when changes are made.

Perhaps, instead of using KVO, you could declare online like:

static var online = false {
    didSet{
        //code to post notification through regular notification center
    }
}

If you're set on using it, this question might point you towards the right direction — it'll involve diving deeper into how KVO works: Is it possible to set up KVO notifications for static variables in objective C?

Community
  • 1
  • 1
TheBrownCoder
  • 1,186
  • 1
  • 10
  • 18
  • @MJQZ1347 Updated answer – TheBrownCoder Jul 06 '16 at 23:04
  • I also thought of `NSNotification` but I am kind of surprised that there is no other way to observe static vars. But I think it is the way to go then. Also: Why not use `didSet` instead of `set`? – MJQZ1347 Jul 06 '16 at 23:07
  • @MJQZ1347 Whoops, my bad. I meant `didSet`. You're right, though, that is kind of strange as it's a pretty useful feature. Maybe it'll come in the future! – TheBrownCoder Jul 06 '16 at 23:16
0

I would suggest property wrapper, I tried the example below and worked perfectly for me:

    @propertyWrapper
    struct StaticObserver<T> {
        private var value:T
        init(value:T) {
            self.value = value
        }
        var wrappedValue: T {
        get {
            // Do your thing
            return self.value
        }
        set {
            // Do your thing before set 
            self.value = newValue
            // Do your thing after set
        }
    }

    @StaticObserver(value: false)
    dynamic static var online:Bool
ideastouch
  • 1,307
  • 11
  • 6