Доступно ли наблюдение значения ключа (KVO) в Swift?
Если да, есть ли какие-либо ключевые различия, которые иначе не присутствовали бы при использовании наблюдения значения ключа в Objective-C?
10 ответов
И да и нет. KVO работает над подклассами NSObject так же, как всегда. Это не работает для классов, которые не подкласс NSObject. Swift не имеет (в настоящее время, по крайней мере) собственной системы наблюдения.
(См. Комментарии о том, как выставить другие свойства как ObjC, чтобы KVO работал с ними)
См. Документацию Apple для полного примера.
Вы можете использовать KVO в Swift, но только для dynamic
свойства NSObject
подкласс. Считайте, что вы хотели наблюдать bar
свойство Foo
учебный класс. В Swift 4 укажите bar
как 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 у нас теперь есть строгая типизация клавиш с использованием символа обратной косой черты (\.bar
является ключом для bar
свойство объекта наблюдения). Кроме того, поскольку он использует шаблон закрытия завершения, нам не нужно вручную удалять наблюдателей (когда token
выходит за рамки видимости, наблюдатель удален за нас) и нам не нужно беспокоиться о вызове super
реализация, если ключ не совпадает. Закрытие вызывается только тогда, когда вызывается этот конкретный наблюдатель. Для получения дополнительной информации см. Видео WWDC 2017, Что нового в Foundation.
В Swift 3, чтобы наблюдать это, это немного сложнее, но очень похоже на то, что делают в Objective-C. А именно, вы бы реализовали observeValue(forKeyPath keyPath:, of object:, change:, context:)
что (а) гарантирует, что мы имеем дело с нашим контекстом (а не то, что наш 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. Таким образом, вы не можете наблюдать дженерики, Свифт struct
типы, Свифт enum
типы и т. д.
Для обсуждения реализации Swift 2 см. Мой оригинальный ответ ниже.
С использованием dynamic
Ключевое слово для достижения КВО с NSObject
Подклассы описаны в разделе " Наблюдение значения ключа " главы " Принятие условных обозначений дизайна какао" руководства "Использование Swift с какао и Objective-C":
Наблюдение значения ключа - это механизм, который позволяет объектам получать уведомления об изменениях указанных свойств других объектов. Вы можете использовать наблюдение значения ключа с классом Swift, если класс наследует от
NSObject
учебный класс. Вы можете использовать эти три шага для реализации наблюдения значения ключа в 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 имеет собственную систему наблюдения за собственными свойствами, но это для класса, задающего собственный код, который будет выполняться при наблюдении за его собственными свойствами. KVO, с другой стороны, предназначен для регистрации, чтобы наблюдать изменения некоторых динамических свойств некоторого другого класса.
И да, и нет:
Да, вы можете использовать те же старые API KVO в Swift для наблюдения за объектами Objective-C.
Вы также можете наблюдатьdynamic
свойства объектов Swift, наследуемых отNSObject
,
Но... Нет, он не слишком типизирован, как можно было ожидать от системы наблюдения Swift.
Использование Swift с какао и Objective-C | Наблюдение за ключевыми значениямиНет, в настоящее время нет встроенной системы наблюдения значений для произвольных объектов Swift.
Да, есть встроенные Обозреватели Собственности, которые строго типизированы.
Но... Нет, они не являются KVO, так как они позволяют только наблюдать за собственными свойствами объектов, не поддерживают вложенные наблюдения ("ключевые пути"), и вы должны явно их реализовать.
Язык программирования 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
,
В дополнение к ответу Роба. Этот класс должен наследовать от 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 Type Совместимость
Вы также можете использовать динамический модификатор, чтобы требовать, чтобы доступ к элементам динамически отправлялся через среду выполнения Objective C, если вы используете такие API, как наблюдение ключ-значение, которые динамически заменяют реализацию метода.
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
И свойство getter и setter вызывается при использовании. Вы можете проверить при работе с КВО. Это пример вычисляемого свойства
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
}
}
}
В настоящее время Swift не поддерживает какой-либо встроенный механизм для наблюдения за изменениями свойств объектов, кроме "self", поэтому нет, он не поддерживает KVO.
Тем не менее, KVO является настолько фундаментальной частью Objective-C и Cocoa, что кажется весьма вероятным, что он будет добавлен в будущем. Текущая документация, кажется, подразумевает это:
Наблюдение значения ключа
Информация ожидается.
Обзор
Возможно использование Combine
без использования NSObject
или Objective-C
Доступность: iOS 13.0+
, macOS 10.15+
, tvOS 13.0+
, watchOS 6.0+
, Mac Catalyst 13.0+
, Xcode 11.0+
Примечание. Необходимо использовать только с классами, не имеющими типов значений.
Код:
Быстрая версия: 5.1.2
import Combine //Combine Framework
//Needs to be a class doesn't work with struct and other value types
class Car {
@Published var price : Int = 10
}
let car = Car()
//Option 1: Automatically Subscribes to the publisher
let cancellable1 = car.$price.sink {
print("Option 1: value changed to \($0)")
}
//Option 2: Manually Subscribe to the publisher
//Using this option multiple subscribers can subscribe to the same publisher
let publisher = car.$price
let subscriber2 : Subscribers.Sink<Int, Never>
subscriber2 = Subscribers.Sink(receiveCompletion: { print("completion \($0)")}) {
print("Option 2: value changed to \($0)")
}
publisher.subscribe(subscriber2)
//Assign a new value
car.price = 20
Выход:
Option 1: value changed to 10
Option 2: value changed to 10
Option 1: value changed to 20
Option 2: value changed to 20
Обратитесь:
Важно отметить, что после обновления Xcode до 7 beta вы можете получить следующее сообщение:"Метод не переопределяет ни один метод из своего суперкласса". Это из-за необязательности аргументов. Убедитесь, что ваш обработчик наблюдения выглядит точно так:
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 и объявляете свои переменные следующим образом, например:
class Theme : NSObject{
dynamic var min_images : Int = 0
dynamic var moreTextSize : CGFloat = 0.0
func myMethod(){
self.setValue(value, forKey: "\(min_images)")
}
}