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

  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 имеет собственную систему наблюдения за собственными свойствами, но это для класса, задающего собственный код, который будет выполняться при наблюдении за его собственными свойствами. 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, что кажется весьма вероятным, что он будет добавлен в будущем. Текущая документация, кажется, подразумевает это:

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

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

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

Обзор

Возможно использование 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)")
   }

}
Другие вопросы по тегам