Имеется ли наблюдение за ключевыми значениями (KVO) в Swift?

Если да, существуют ли какие-либо ключевые отличия, которые иначе не присутствовали при использовании наблюдения за ключевыми значениями в Objective-C?

Ответ 1

Да и нет. KVO работает над подклассами NSObject так же, как и всегда. Он не работает для классов, которые не подклассы NSObject. В Swift нет (по крайней мере, сейчас) собственной системы наблюдения.

(См. комментарии о том, как показывать другие свойства как ObjC, поэтому KVO работает над ними)

Подробную информацию см. в Apple Documentation.

Ответ 2

Вы можете использовать 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 это было значительно упрощено, как описано в разделе Использование наблюдения значения ключа в 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)")
        }
    }
}

Обратите внимание, что в Swift 4 теперь мы имеем строгую типизацию траекторий клавиш с использованием символа обратной косой черты (\.bar - это траектория клавиш для свойства bar наблюдаемого объекта). Кроме того, поскольку он использует шаблон закрытия завершения, нам не нужно вручную удалять наблюдателей (когда token выпадает из области видимости, наблюдатель удаляется для нас), а также нам не нужно беспокоиться о вызове super реализации, если ключ не не совпадают Закрытие вызывается только тогда, когда вызывается этот конкретный наблюдатель. Для получения дополнительной информации см. Видео WWDC 2017, Что нового в Foundation.

В 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])")
    }

}

Обратите внимание, что вы можете наблюдать только свойства, которые могут быть представлены в Objective-C. Таким образом, вы не можете наблюдать обобщенные типы, типы struct Swift, типы enum Swift и т.д.

Для обсуждения реализации Swift 2 см. Мой оригинальный ответ ниже.


Использование dynamic ключевого слова для достижения KVO с подклассами NSObject описано в разделе "Наблюдение значения ключа" главы " Принятие конвенций проектирования cocoa" руководства "Использование Swift с cocoa и Objective-C":

Наблюдение значения ключа - это механизм, который позволяет объектам получать уведомления об изменениях указанных свойств других объектов. Вы можете использовать наблюдение значения ключа с классом Swift, если этот класс наследуется от класса NSObject. Вы можете использовать эти три шага для реализации наблюдения значения ключа в Swift.

  1. Добавьте dynamic модификатор к любому свойству, которое вы хотите наблюдать. Для получения дополнительной информации о dynamic см. Раздел "Требование динамической отправки".

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
    
  2. Создайте глобальную переменную контекста.

    private var myContext = 0
    
  3. Добавьте наблюдателя для ключевого пути и переопределите 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 с cocoa и Objective-C", которое было адаптировано для Swift 3, но оно все еще работает, как описано в верхней части этого ответа.]


Стоит отметить, что Swift имеет свою собственную родной наблюдателя свойство системы, но для класса указав свой собственный код, который будет выполняться при наблюдении его собственных свойств. KVO, с другой стороны, предназначен для регистрации, чтобы наблюдать изменения некоторых динамических свойств некоторого другого класса.

Ответ 3

И да и нет:

  • Да, вы можете использовать те же старые KVO API в Swift, чтобы наблюдать объекты Objective-C.
    Вы также можете наблюдать dynamic свойства объектов Swift, наследующих от NSObject.
    Но... Нет, он не сильно набрал, как вы могли бы ожидать, как будет выглядеть система наблюдения Swift.
    Использование Swift с Cocoa и Objective-C | Наблюдение за ключевыми значениями

  • Нет, в настоящее время нет системы наблюдения за встроенным значением для произвольных объектов Swift.

  • Да, есть встроенные Property Observer, которые строго типизированы.
    Но... Нет они не являются КВО, поскольку они позволяют только наблюдать за собственными свойствами объектов, не поддерживают вложенные наблюдения ( "пути ключей" ), и вам необходимо их явно реализовать. < ш > Быстрый язык программирования | Наблюдатели за качеством

  • Да, вы можете реализовать явное наблюдение за значением, которое будет строго типизировано и позволит добавлять несколько обработчиков из других объектов и даже поддерживать вложенные / "пути ключей".
    Но... Нет это не будет KVO, так как он будет работать только для свойств, которые вы реализуете как наблюдаемые. Вы можете найти библиотеку для реализации такой оценки стоимости здесь:
    Observable-Swift - KVO для Swift - наблюдение за состоянием и события

Ответ 4

Пример может помочь немного здесь. Если у меня есть экземпляр 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)")
}

Ответ 5

Да.

KVO требует динамической отправки, поэтому вам просто нужно добавить модификатор dynamic к методу, свойству, индексу или инициализатору:

dynamic var foo = 0

Модификатор dynamic гарантирует, что ссылки на объявление будут динамически отправляться и доступны через objc_msgSend.

Ответ 6

В настоящее время Swift не поддерживает встроенный механизм наблюдения изменений свойств объектов, отличных от "я", поэтому нет, он не поддерживает KVO.

Однако KVO является такой фундаментальной частью Objective-C и Cocoa, что вполне вероятно, что он будет добавлен в будущем. В текущей документации, похоже, подразумевается следующее:

Наблюдение за ключевыми значениями

Информация о предстоящем.

Использование Swift с Cocoa и Objective-C

Ответ 7

В дополнение к Робу ответ. Этот класс должен наследоваться от 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
        }
    }
}

Ответ 8

Важно отметить, что после обновления Xcode до 7 бета вы можете получить следующее сообщение: "Метод не отменяет какой-либо метод из его суперкласса" . Это из-за опциональности аргументов. Убедитесь, что ваш обработчик наблюдения выглядит следующим образом:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer<Void>)

Ответ 9

Это может оказаться полезным для немногих людей -

// 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. Вы можете использовать этот код с небольшими изменениями.

Ответ 10

Другой пример для тех, кто сталкивается с проблемой с такими типами, как 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)")
   }

}