【问题标题】:Swift KVO - Observing enum propertiesSwift KVO - 观察枚举属性
【发布时间】:2015-02-02 06:24:50
【问题描述】:

我正在组装一个类,该类具有多个由枚举定义的状态,以及一个返回实例当前状态的只读属性“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 等,但是否有一个简单的替代解决方法可以保留枚举提供的类型安全性?

文斯。

【问题讨论】:

  • 关于这个老问题,这些天只需将枚举标记为 Int 就可以了。

标签: swift enums key-value-observing


【解决方案1】:

也许这仅在 swift 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

【讨论】:

  • 谢谢!对我来说,它只需确保枚举是Int 并且前面有@objc。非常整洁。
  • 我认为这应该被接受为最佳答案。公开暴露枚举和原始值看起来并不整洁,尤其是在可以避免的情况下。
  • 这是(今天)迄今为止的正确答案。这是一个很好的例子,说明关于 SO 的好的但旧的答案会变得错误。
  • 非常感谢!这很棒。
【解决方案2】:

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

您可以对“原始”属性进行 KVO,然后在其更改时引用真正的枚举属性。

这显然是一种 hack,但对我来说,这比完全放弃枚举并失去所有好处要好。

例如。

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. 观察回调代码作为闭包而不是必须实现 观察ValueForKeyPath:BlahBlah...
  3. 无需确保您删除Observer,它会为您处理好。

实用程序类称为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
    }
}

【讨论】:

  • 好答案。效果很好。使用原始值仅检测更改,但引用枚举而不是原始值来确定新状态。我不会那样做的。谢谢。
  • 不客气。我花了一段时间才得出那个解决方案!需要注意的一件事:如果您的枚举原始值是 Int 并且您希望您的属性是可选的,那么您不能使用它,因为您不能拥有 Int?属性为动态的。
  • 虽然,你可以转换成 NSNumber 吗?我猜。
  • 这种情况下应该问题不大;因为这些对象总是有一个状态,所以它永远不需要是可选的。
  • 这是一个优秀的助手类。它简化了实现 KVO 带来的一些麻烦,所以我将它合并到我的应用程序中。再次感谢。
猜你喜欢
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-20
  • 1970-01-01
相关资源
最近更新 更多