是否可以在Swift中使用键值观察(KVO)?

jon*_*ley 163 key-value-observing swift

如果是这样,在Objective-C中使用键值观察时是否存在其他不存在的关键差异?

Rob*_*Rob 146

您可以在Swift中使用KVO,但仅限于子类的dynamic属性NSObject.考虑一下你想要观察bar一个Foo类的属性.在Swift 4中,在子类中指定bardynamicproperty NSObject:

class Foo: NSObject {
    @objc dynamic var bar = 0
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以注册以观察bar属性的更改.在Swift 4和Swift 3.2中,这已大大简化,如在Swift使用键值观察中所述:

class MyObject {
    private var token: NSKeyValueObservation

    var objectToObserve = Foo()

    init() {
        token = objectToObserve.observe(\.bar) { [weak self] object, change in  // the `[weak self]` is to avoid strong reference cycle; obviously, if you don't reference `self` in the closure, then `[weak self]` is not needed
            print("bar property is now \(object.bar)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意,在Swift 4中,我们现在使用反斜杠字符(这\.barbar被观察对象的属性的键路径)强键入键路径.此外,因为它使用了完成闭包模式,所以我们不必手动删除观察者(当token范围超出范围时,观察者就会被移除),super如果密钥没有,我们也不必担心调用实现比赛.只有在调用此特定观察者时才会调用闭包.有关更多信息,请参阅WWDC 2017视频,基金会的新功能.

在Swift 3中,为了观察它,它有点复杂,但与Objective-C中的相似.也就是说,你应该实现observeValue(forKeyPath keyPath:, of object:, change:, context:)哪个(a)确保我们处理我们的上下文(而不是我们的super实例注册要观察的东西); 然后(b)根据需要处理或传递给super实施.并确保在适当的时候以观察者身份移除自己.例如,您可以在取消分配时删除观察者:

在Swift 3中:

class MyObject: NSObject {
    private var observerContext = 0

    var objectToObserve = Foo()

    override init() {
        super.init()

        objectToObserve.addObserver(self, forKeyPath: #keyPath(Foo.bar), options: [.new, .old], context: &observerContext)
    }

    deinit {
        objectToObserve.removeObserver(self, forKeyPath: #keyPath(Foo.bar), context: &observerContext)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard context == &observerContext else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
            return
        }

        // do something upon notification of the observed object

        print("\(keyPath): \(change?[.newKey])")
    }

}
Run Code Online (Sandbox Code Playgroud)

注意,您只能观察可以在Objective-C中表示的属性.因此,您无法观察泛型,Swift struct类型,Swift enum类型等.

有关Swift 2实现的讨论,请参阅下面的原始答案.


使用dynamic关键字实现带子NSObject类的KVO 在使用Swift with Cocoa和Objective-C指南的采用可可设计约定章节的键值观察部分中描述:

键值观察是一种机制,允许对象通知其他对象的指定属性的更改.只要类继承自类,您就可以对Swift类使用键值观察NSObject.您可以使用这三个步骤在Swift中实现键值观察.

  1. dynamic修改器添加到要观察的任何属性.有关更多信息dynamic,请参阅需要动态调度.

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建全局上下文变量.

    private var myContext = 0
    
    Run Code Online (Sandbox Code Playgroud)
  3. 为key-path添加一个观察者,并覆盖该observeValueForKeyPath:ofObject:change:context:方法,然后删除中的观察者deinit.

    class MyObserver: NSObject {
        var objectToObserve = MyObjectToObserve()
        override init() {
            super.init()
            objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
        }
    
        override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
            if context == &myContext {
                if let newValue = change?[NSKeyValueChangeNewKey] {
                    print("Date changed: \(newValue)")
                }
            } else {
                super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
            }
        }
    
        deinit {
            objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

[注意,此KVO讨论随后已从使用Swift with Cocoa和Objective-C指南中删除,该指南已针对Swift 3进行了修改,但它仍然按照本答案顶部的说明进行操作.


值得注意的是,Swift有自己的本地属性观察器系统,但这是指定一个类,它将在观察自己的属性时执行.另一方面,KVO旨在注册以观察某些其他类的某些动态属性的变化.

  • @devth,据我所知,如果子类或超类也为同一个变量注册KVO观察者,则会多次调用observeValueForKeyPath.在这种情况下,Context可用于区分自己的通知.更多相关信息:http://www.dribin.org/dave/blog/archives/2008/09/24/proper_kvo_usage/ (3认同)

Cat*_*Man 105

是的,不是.KVO对NSObject子类的工作原理与它一如既往.它不适用于没有NSObject子类的类.斯威夫特(目前至少)没有自己的原生观察系统.

(请参阅有关如何将其他属性公开为ObjC的注释,以便KVO对其进行处理)

有关完整示例,请参阅Apple文档.

  • 从Xcode 6 beta 5开始,您可以在任何Swift类上使用`dynamic`关键字来启用KVO支持. (74认同)
  • 为@fabb万岁!为清楚起见,`dynamic`关键字位于要使键值可观察的属性上. (7认同)
  • 因为@ fabb的注释对我来说并不清楚:对于你想要符合KVO的类里面的任何*属性*,使用`dynamic`关键字(而不是类本身的`dynamic`关键字).这对我有用! (6认同)
  • 关于`dynamic`关键字的解释可以在Apple Developer Library的[使用Swift with Cocoa和Objective-C部分]中找到(https://developer.apple.com/library/prerelease/mac/documentation/Swift/Conceptual/ BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#// apple_ref/DOC/UID/TP40014216-CH7-XID_8). (5认同)

sla*_*zyk 91

是和否:

  • 是的,您可以在Swift中使用相同的旧KVO API来观察Objective-C对象.
    您还可以观察dynamic继承自的Swift对象的属性NSObject.
    但是...... 不,它没有强烈打字,因为你可以期待Swift原生观察系统.
    使用Swift与Cocoa和Objective-C | 关键价值观察

  • ,目前没有任意Swift对象的内置值观测系统.

  • 是的,有内置的Property Observers,它们是强类型的.
    但是...... 不,它们不是KVO,因为它们只允许观察对象自己的属性,不支持嵌套观察("关键路径"),你必须明确地实现它们.
    Swift编程语言| 财产观察员

  • 是的,您可以实现显式值观察,它将是强类型的,并允许从其他对象添加多个处理程序,甚至支持嵌套/"键路径".
    但是...... 不,它不会是KVO,因为它只适用于你实现为可观察的属性.
    您可以在此处找到用于实现此类值观察的库:
    Observable-Swift - 用于Swift的KVO - 值观察和事件


Pau*_*son 10

一个例子可能对此有所帮助.如果我有一个实例model类的Model使用属性namestate我可以观察到这些属性:

let options = NSKeyValueObservingOptions([.New, .Old, .Initial, .Prior])

model.addObserver(self, forKeyPath: "name", options: options, context: nil)
model.addObserver(self, forKeyPath: "state", options: options, context: nil)
Run Code Online (Sandbox Code Playgroud)

对这些属性的更改将触发对以下内容的调用:

override func observeValueForKeyPath(keyPath: String!,
    ofObject object: AnyObject!,
    change: NSDictionary!,
    context: CMutableVoidPointer) {

        println("CHANGE OBSERVED: \(change)")
}
Run Code Online (Sandbox Code Playgroud)

  • 如果我没记错的话,observeValueForKeyPath 调用方法是针对 Swift2 的。 (2认同)

Bry*_*uby 9

是.

KVO需要动态分派,因此您只需将dynamic修饰符添加到方法,属性,下标或初始化程序中:

dynamic var foo = 0

dynamic修改确保对声明的引用将被动态调度,并通过访问objc_msgSend.


onm*_*133 6

除了Rob的回答.该类必须继承NSObject,并且我们有3种方法来触发属性更改

使用setValue(value: AnyObject?, forKey key: String)NSKeyValueCoding

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        setValue(NSDate(), forKey: "myDate")
    }
}
Run Code Online (Sandbox Code Playgroud)

使用willChangeValueForKeydidChangeValueForKey来自NSKeyValueObserving

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        willChangeValueForKey("myDate")
        myDate = NSDate()
        didChangeValueForKey("myDate")
    }
}
Run Code Online (Sandbox Code Playgroud)

使用dynamic.请参阅Swift类型兼容性

您还可以使用动态修饰符来要求通过Objective-C运行时动态调度成员访问,如果您使用的是键值观察等动态替换方法实现的API.

class MyObjectToObserve: NSObject {
    dynamic var myDate = NSDate()
    func updateDate() {
        myDate = NSDate()
    }
}
Run Code Online (Sandbox Code Playgroud)

使用时会调用属性getter和setter.您可以验证何时使用KVO.这是计算属性的示例

class MyObjectToObserve: NSObject {
    var backing: NSDate = NSDate()
    dynamic var myDate: NSDate {
        set {
            print("setter is called")
            backing = newValue
        }
        get {
            print("getter is called")
            return backing
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Col*_*inE 5

目前Swift不支持任何内置机制来观察"self"以外的对象的属性更改,所以不,它不支持KVO.

然而,KVO是Objective-C和Cocoa的基本组成部分,它很可能会在将来添加.目前的文件似乎暗示了这一点:

关键价值观察

即将发布的信息.

使用Swift与Cocoa和Objective-C

  • 是的,现在已于2014年9月实施 (4认同)
  • 显然,您现在引用的指南描述了如何在Swift中执行KVO. (2认同)