Swift KVO - Observing enum properties

Vince O'Sullivan picture Vince O'Sullivan · Dec 4, 2014 · Viewed 7.1k times · Source

I'm assembling a class which has several states, as defined by an enum, and a read-only property "state" which returns the instance's current state. I was hoping to use KVO techniques to observe changes in state but this doesn't seem possible:

dynamic var state:ItemState // Generates compile-time error: Property cannot be marked dynamic because its type cannot be represented in Objective-C

I guess I could represent each state as an Int or String, etc. but is there a simple alternative workaround that would preserve the type safety that the enum would otherwise provide?

Vince.

Answer

ɲeuroburɳ picture ɲeuroburɳ · Aug 11, 2015

Perhaps this is only available in swift 2+, but you can make an enum property directly observable without having to refer to its rawValue. It does come with some limitations however.

  1. have the containing class extend from NSObject (directly or indirectly)
  2. mark the enum with @objc
  3. extend the enum from Int
  4. declare the property as dynamic.
class SomeModel : NSObject {                          // (1) extend from NSObject
    @objc                                             // (2) mark enum with @objc
    enum ItemState : Int, CustomStringConvertible {   // (3) extend enum from Int
        case Ready, Set, Go

        // implementing CustomStringConvertible for example output
        var description : String {
            switch self {
            case .Ready: return "Ready"
            case .Set: return "Set"
            case .Go: return "Go"
            }
        }
    }

    dynamic var state = ItemState.Ready               // (4) declare property as dynamic
}

Elsewhere:

class EnumObserverExample : NSObject {
    private let _model : SomeModel

    init(model:SomeModel) {
        _model = model
        super.init()
        _model.addObserver(self, forKeyPath:"state", options: NSKeyValueObservingOptions.Initial, context: nil)
    }
    deinit {
        _model.removeObserver(self, forKeyPath:"state", context: nil)
    }

    override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        if "state" == keyPath {
            print("Observed state change to \(_model.state)")
        }
    }
}

let model = SomeModel()
let observer = EnumObserverExample(model:model)
model.state = .Set
model.state = .Go

Outputs:

Observed state change to Ready    (because .Initial was specified)
Observed state change to Set
Observed state change to Go