2014-12-04 123 views
4

我正在组装一个具有几个状态的类,这个状态由enum定义,并且只读属性“state”返回实例的当前状态。我希望用KVO技术来观察状态的变化,但是这似乎并不可能:Swift KVO - 观察枚举属性

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

我想我可以代表每个状态为int或字符串,等等,但有一个简单的替代解决方法,将保持类型的安全性,枚举会另外提供?

Vince。

回答

13

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

您可以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 
     } 
    } 
} 

补充:

如果你正在写的是正在做的雨燕观察类下面是一个方便的工具类采取一些痛苦的了。 的好处是:

  1. 无需您的观察员子类NSObject。
  2. 观察回调代码为封闭,而不是实现 observeValueForKeyPath:BlahBlah ...
  3. 没有必要,以确保您removeObserver,它照顾你。

实用类被称为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 
    } 
} 
+0

很好的回答。很好地工作。使用原始值来检测变化,但引用枚举而不是原始数据来确定新状态。我不会那样做的。谢谢。 – 2014-12-04 12:26:39

+0

不客气。花了一段时间才找到解决方案!有一件事要注意:如果你的枚举原始值是Int并且你希望你的属性是可选的,你不能使用它,因为你不能有一个Int?财产如同动态。 – 2014-12-04 12:31:49

+0

虽然,你可以转换为NSNumber?我猜。 – 2014-12-04 12:56:26

11

或许这只是迅速2+可用的,但你可以让一个枚举财产直接观察到的,而不必引用其rawValue。但它确实带来了一些限制。

  1. 具有包含类从NSObject(直接或间接)延伸
  2. 标记与@objc
  3. 枚举从Int
  4. 延伸枚举声明属性作为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 
} 

在别处:

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 
+0

谢谢!对我来说,它只是确保枚举是'Int'并且在它前面有'@ objc'。井井有条。 – 2016-01-26 14:44:40