Доступно ли наблюдение за ключевыми значениями (KVO) в Swift?
Если да, есть ли какие-либо ключевые различия, которые не присутствовали при использовании наблюдения ключевого значения в Objective-C?
10 ответов
да и нет. KVO работает на подклассах NSObject так же, как и всегда. Это не работает для классов, которые не подкласс NSObject. Swift не имеет (по крайней мере, в настоящее время) собственной собственной системы наблюдения.
(см. комментарии о том, как выставлять другие свойства как ObjC, чтобы KVO работал над ними)
посмотреть Документация Apple полный пример.
вы можете использовать KVO в Swift, но только для dynamic
свойства NSObject
подкласс. Считайте, что вы хотели наблюдать bar
свойства Foo
класса. В Swift 4 укажите bar
as dynamic
свойства NSObject
подкласс:
class Foo: NSObject {
@objc dynamic var bar = 0
}
затем вы можете зарегистрироваться, чтобы наблюдать изменения bar
собственность. В Swift 4 и Swift 3.2, это было значительно упрощено:
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)")
}
}
}
Примечание, в Swift 4, мы теперь имеем сильный печатать keypaths используя символ обратной косой черты (\.bar
является ключевым для bar
свойства объекта). Кроме того, поскольку он использует шаблон закрытия завершения, нам не нужно вручную удалять наблюдателей (когда token
выпадает из области видимости, наблюдатель удаляется для нас), и нам не нужно беспокоиться о вызове super
реализации, если ключ не совпадает. Закрытие вызывается только тогда, когда вызывается этот конкретный наблюдатель. Дополнительные сведения см. В разделе WWDC 2017 video, что нового в Foundation.
в Swift 3, чтобы наблюдать это, это немного сложнее, но очень похоже на то, что делается в Objective-C. А именно, вы бы реализовали observeValue(forKeyPath keyPath:, of object:, change:, context:)
который (a) гарантирует, что мы имеем дело с нашим контекстом (а не с чем-то, что наше super
экземпляр зарегистрировался для наблюдения); а затем (Б) либо обработайте его, либо передайте его 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])")
}
}
обратите внимание, вы можете наблюдать только свойства, которые могут быть представлены в Objective-C. Таким образом, вы не можете наблюдать дженерики, Swift struct
типы, Swift enum
типы и т. д.
для обсуждения реализации Swift 2 см. мой первоначальный ответ ниже.
С помощью dynamic
ключевое слово для достижения KVO с NSObject
подклассы описаны в the на Принятие Конвенций По Дизайну Какао глава использование Swift с какао и Objective-C руководство:
key-value observing-это механизм, позволяющий уведомлять объекты об изменениях заданных свойств других объектов. Вы можете использовать наблюдение за значением ключа с классом Swift, если класс наследует от
NSObject
класса. Вы можете использовать эти три шага для осуществления key-значение наблюдения в Swift.
добавить
dynamic
модификатор для любого свойства, которое вы хотите наблюдать. Для получения дополнительной информации оdynamic
см. Требуется Динамическая Отправка.class MyObjectToObserve: NSObject { dynamic var myDate = NSDate() func updateDate() { myDate = NSDate() } }
создайте глобальную контекстную переменную.
private var myContext = 0
добавьте наблюдателя для ключевого пути и переопределите
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) } }
[обратите внимание, что это обсуждение KVO впоследствии было удалено из использование Swift с какао и Objective-C руководство, которое было адаптировано для Swift 3, но оно все еще работает, как указано в верхней части этого ответа.]
стоит отметить, что Swift имеет свой собственный уроженца наблюдатель собственность system, но это для класса, указывающего свой собственный код, который будет выполняться при наблюдении его собственных свойств. Кво, на с другой стороны, предназначен для регистрации изменений в некоторых динамических свойствах другого класса.
и да и нет:
да, вы можете использовать те же старые API KVO в Swift для наблюдения объектов Objective-C.
Вы также можете наблюдатьdynamic
свойства объектов Swift, наследуемых отNSObject
.
Но... нет это не сильно типизировано, как вы могли бы ожидать, что система наблюдения Swift native будет.
использование Swift с какао и Objective - C / Key значение наблюдениянет, в настоящее время нет встроенной системы наблюдения значений для произвольных объектов Swift.
да, есть строение -Наблюдатели За Недвижимостью, которые сильно типизированы.
Но... нет они не являются кво, поскольку они позволяют только наблюдать за собственными свойствами объектов, не поддерживают вложенные наблюдения ("ключевые пути"), и вы должны явно реализовать их.
Swift Язык Программирования / Свойства Наблюдателейда, вы можете реализовать явное наблюдение значений, которое будет строго типизировано и позволит добавлять несколько обработчиков из других объектов и даже поддерживать вложенность / "ключевые пути".
Но... нет это не будет KVO, так как он будет работать только для свойств, которые вы реализуете как наблюдаемые.
Вы можете найти библиотеку для реализации такого значения наблюдая здесь:
Observable-Swift-KVO для Swift - значение наблюдения и событий
пример может немного помочь здесь. Если у меня есть экземпляр model
класса Model
С атрибутами name
и state
Я могу наблюдать те атрибуты:
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)
изменения этих свойств вызовут вызов:
override func observeValueForKeyPath(keyPath: String!,
ofObject object: AnyObject!,
change: NSDictionary!,
context: CMutableVoidPointer) {
println("CHANGE OBSERVED: \(change)")
}
да.
KVO требует динамической отправки, поэтому вам просто нужно добавить dynamic
модификатор метода, свойства, индекса или инициализатора:
dynamic var foo = 0
на dynamic
модификатор гарантирует, что ссылки на объявление будут динамически отправлены и доступны через objc_msgSend
.
В настоящее время Swift не поддерживает встроенный механизм наблюдения за изменениями свойств объектов, отличных от "self", поэтому нет, он не поддерживает KVO.
однако KVO является такой фундаментальной частью Objective-C и Cocoa, что вполне вероятно, что она будет добавлена в будущем. Текущая документация, по-видимому, подразумевает следующее:
в дополнение к ответу Роба. Этот класс должен наследовать от NSObject
, и у нас есть 3 способа вызвать изменение свойства
использовать setValue(value: AnyObject?, forKey key: String)
С NSKeyValueCoding
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
setValue(NSDate(), forKey: "myDate")
}
}
использовать willChangeValueForKey
и didChangeValueForKey
С NSKeyValueObserving
class MyObjectToObserve: NSObject {
var myDate = NSDate()
func updateDate() {
willChangeValueForKey("myDate")
myDate = NSDate()
didChangeValueForKey("myDate")
}
}
использовать dynamic
. См.Совместимость Типа Swift
вы также можете использовать динамический модификатор, чтобы потребовать, чтобы доступ к членам динамически отправлялся через среду выполнения Objective-C, если вы используете API, такие как key-value observing, которые динамически заменяют реализацию метода.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
и свойство 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
}
}
}
важно отметить, что после обновления вашего Xcode to бета 7 вы можете получить следующее сообщение: "метод не переопределяет ни один метод из своего суперкласса". Это из-за опциональности аргументов. Убедитесь, что обработчик наблюдений выглядит следующим образом:
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)
Это может оказаться полезным для немногих людей -
// MARK: - KVO
var observedPaths: [String] = []
func observeKVO(keyPath: String) {
observedPaths.append(keyPath)
addObserver(self, forKeyPath: keyPath, options: [.old, .new], context: nil)
}
func unObserveKVO(keyPath: String) {
if let index = observedPaths.index(of: keyPath) {
observedPaths.remove(at: index)
}
removeObserver(self, forKeyPath: keyPath)
}
func unObserveAllKVO() {
for keyPath in observedPaths {
removeObserver(self, forKeyPath: keyPath)
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let keyPath = keyPath {
switch keyPath {
case #keyPath(camera.iso):
slider.value = camera.iso
default:
break
}
}
}
Я использовал KVO таким образом в Swift 3. Этот код можно использовать с небольшими изменениями.
другой пример для тех, кто сталкивается с проблемой с такими типами, как Int? и CGFloat?. Вы просто устанавливаете класс как подкласс NSObject и объявляете свои переменные следующим образом e.g:
class Theme : NSObject{
dynamic var min_images : Int = 0
dynamic var moreTextSize : CGFloat = 0.0
func myMethod(){
self.setValue(value, forKey: "\(min_images)")
}
}