- Это ключ-значение наблюдений (кво) доступны в Swift?


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

10 152

10 ответов:

да и нет. Кво работает на подклассах 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 экземпляр зарегистрировался для наблюдения); а затем (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. Таким образом, вы не можете наблюдать дженерики, Swift struct типы, Swift enum типы, etc.

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


С помощью dynamic ключевое слово для достижения кво с NSObject подклассы описаны в элемент на Принятие Конвенций По Дизайну Какао глава использование Swift с какао и Objective-C руководство:

key-value observing-это механизм, который позволяет уведомлять объекты об изменениях заданных свойств других объектов. Вы можете использовать наблюдение за значением ключа с помощью класса 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)
        }
    }
    

[обратите внимание, что это обсуждение кво впоследствии было удалено из использование Swift с какао и Objective-C руководство, которое было адаптировано для Swift 3, но оно все еще работает, как указано в верхней части этого ответа.]


стоит отметить, что Swift имеет свой собственный уроженца наблюдатель собственность система, но это для класса, указывающего свой собственный код, который будет выполняться при наблюдении за его собственными свойствами. Кво на с другой стороны, предназначен для регистрации, чтобы наблюдать изменения в каком-то динамическом свойстве какого-то другого класса.

и да и нет:

  • да, вы можете использовать те же старые API KVO в Swift для наблюдения объектов Objective-C.
    Вы также можете наблюдать dynamic свойства объектов Swift, наследуемых от NSObject.
    Но... нет это не сильно типизировано, как вы могли бы ожидать, что Swift native observation system будет.
    использование Swift с какао и Objective-C / Key Value Observing

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

  • да, есть строение -Наблюдатели За Недвижимостью, которые строго типизированы.
    Но... нет они не являются кво, так как они позволяют только наблюдать за собственными свойствами объектов, не поддерживают вложенные наблюдения ("ключевые пути"), и вы должны явно реализовать их.
    Язык Программирования 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

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

В настоящее время Swift не поддерживает встроенный механизм для наблюдения за изменениями свойств объектов, отличных от "self", поэтому нет, он не поддерживает 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, которые динамически заменяют реализацию метода.

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 до бета 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
        }
    }
}

я использовал кво таким образом в 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)")
   }

}