Swift KVO - 观察枚举属性

时间:2014-12-04 11:29:49

标签: swift enums key-value-observing

我正在组装一个具有多个状态的类,如枚举所定义的,以及一个只读属性" state"它返回实例的当前状态。我希望使用KVO技术来观察状态的变化,但这似乎不可能:

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

我想我可以将每个州表示为Int或String等,但是有一个简单的替代解决方法可以保留enum本来提供的类型安全性吗?

文斯。

2 个答案:

答案 0 :(得分:14)

前一段时间我遇到了同样的问题。 最后,我使用了枚举状态,并添加了一个额外的“原始”属性,该属性由主状态属性上的属性观察者设置。

您可以KVO'原始'属性,但在更改时引用真实枚举属性。

这显然有点像黑客,但对我来说,这比完全放弃枚举并失去所有好处要好。

例如

class Model : NSObject {

    enum AnEnumType : String {
        case STATE_A = "A"
        case STATE_B = "B"
    }

    dynamic private(set) var enumTypeStateRaw : String?

    var enumTypeState : AnEnumType? {
        didSet {
            enumTypeStateRaw = enumTypeState?.rawValue
        }
    }
}

附加:

如果您正在编写正在Swift中进行观察的类,这里有一个方便的实用程序类来消除一些痛苦。 好处是:

  1. 您的观察者不需要继承NSObject。
  2. 观察回调代码作为闭包而不是必须实现 observeValueForKeyPath:BlahBlah ...
  3. 无需确保删除服务器,它会为您照顾。
  4. 实用程序类名为KVOObserver,示例用法为:

    class ExampleObserver {
    
        let model : Model
        private var modelStateKvoObserver : KVOObserver?
    
        init(model : Model) {
    
            self.model = model
    
            modelStateKvoObserver = KVOObserver.observe(model, keyPath: "enumTypeStateRaw") { [unowned self] in
                println("new state = \(self.model.enumTypeState)")
            }
        }
    }
    

    注意捕获列表中的[unowned self]以避免参考周期。

    这是KVOObserver ...

    class KVOObserver: NSObject {
    
        private let callback: ()->Void
        private let observee: NSObject
        private let keyPath: String
    
        private init(observee: NSObject, keyPath : String, callback: ()->Void) {
            self.callback = callback
            self.observee = observee
            self.keyPath = keyPath;
        }
    
        deinit {
            println("KVOObserver deinit")
            observee.removeObserver(self, forKeyPath: keyPath)
        }
    
        override func observeValueForKeyPath(keyPath: String,
            ofObject object: AnyObject,
            change: [NSObject : AnyObject],
            context: UnsafeMutablePointer<()>) {
                println("KVOObserver: observeValueForKey: \(keyPath), \(object)")
                self.callback()
        }
    
        class func observe(object: NSObject, keyPath : String, callback: ()->Void) -> KVOObserver {
            let kvoObserver = KVOObserver(observee: object, keyPath: keyPath, callback: callback)
            object.addObserver(kvoObserver, forKeyPath: keyPath, options: NSKeyValueObservingOptions.New | NSKeyValueObservingOptions.Initial, context: nil)
            return kvoObserver
        }
    }
    

答案 1 :(得分:14)

也许这只适用于swift 2+,但你可以直接观察enum属性而无需引用它的rawValue。但它确实有一些限制。

  1. 让包含类从NSObject(直接或间接)
  2. 扩展
  3. 使用@objc
  4. 标记枚举
  5. Int
  6. 扩展枚举
  7. 将该属性声明为dynamic
  8. 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
    }
    

    其他地方:

    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
    

    输出:

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