Доступно ли наблюдение за ключевыми значениями (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.

  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 с какао и 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)")
   }

}