Swift'te anahtar / değer gözlemi (KVO) var mı?


174

Öyleyse, Objective-C'de anahtar / değer gözlemini kullanırken başka türlü mevcut olmayan önemli farklar var mı?


2
Swift'in bir UIKit arayüzünde KVO'nun kullanıldığını gösteren örnek bir proje: github.com/jameswomack/kvo-in-swift
james_womack

@JanDvorak Konuyla ilgili güzel bir tanıtım olan KVO Programlama Kılavuzu'na bakın .
Rob

1
Sorunuzun yanıtı olmasa da, didset () işlevini kullanarak da eylemler başlatabilirsiniz.
Vincent

Kullandığınız zaman bir Swift4 hatası olduğunu unutmayın .initial. Çözüm için buraya bakın . Apple belgelerini görmenizi tavsiye ederim . Son zamanlarda güncellendi ve birçok önemli notu kapsıyor. Ayrıca Rob'un diğer cevabına
Honey

Yanıtlar:


107

(Yeni bilgi eklemek için düzenlendi): Birleştir çerçevesini kullanmanın KVO kullanmak yerine istediğinizi gerçekleştirmenize yardımcı olup olamayacağını düşünün

Evet ve hayır. KVO, NSObject alt sınıflarında her zaman olduğu gibi çalışır. NSObject alt sınıfı olmayan sınıflar için çalışmaz. Swift'in (şu anda en azından) kendi yerel gözlem sistemi yoktur.

(ObjC olarak diğer özelliklerin nasıl gösterileceğini görmek için yorumlara bakın, böylece KVO bunlarda çalışır)

Tam bir örnek için Apple Belgelerine bakın .


74
Xcode 6 beta 5'ten bu yana dynamic, KVO desteğini etkinleştirmek için herhangi bir Swift sınıfındaki anahtar kelimeyi kullanabilirsiniz .
fabb

7
@Oabb için Yaşasın! Açıklık olması açısından, dynamicanahtar kelime , anahtar / değer çiftini gözlemlenebilir yapmak istediğiniz mülke gider.
Jerry

5
dynamicAnahtar kelimeye ilişkin açıklama , Apple Developer Library'nin Swift'i Kakao ve Objective-C ile Kullanma bölümünde bulunabilir .
Imanou Petit

6
Bu @ fabb'ın yorumundan bana açık olmadığı için: KVO uyumlu olmasını istediğiniz sınıfın içindeki dynamicherhangi bir özellik için anahtar kelimeyi kullanın ( dynamicsınıfın kendisindeki anahtar kelimeyi değil ). Bu benim için çalıştı!
Tim Camber

1
Pek sayılmaz; "dışarıdan" yeni bir didSet kaydedemezsiniz, derleme zamanında bu türün bir parçası olması gerekir.
Catfish_Man

155

Swift'te KVO kullanabilirsiniz, ancak yalnızca alt sınıfın dynamicözellikleri için kullanabilirsiniz NSObject. barBir Foosınıfın özelliğini gözlemlemek istediğinizi düşünün . Swift 4'te alt sınıfınızda özellik barolarak belirtin :dynamicNSObject

class Foo: NSObject {
    @objc dynamic var bar = 0
}

Daha sonra bartesisteki değişiklikleri gözlemlemek için kayıt olabilirsiniz . Swift 4 ve Swift 3.2'de, Swift'te Anahtar / Değer Gözlemlemesini Kullanma bölümünde belirtildiği gibi bu büyük ölçüde basitleştirilmiştir :

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'te, şimdi ters eğik çizgi karakterini kullanarak güçlü bir tuş yolları yazmamız var ( gözlemlenen nesnenin özelliği \.bariçin anahtar yolu bar). Ayrıca, tamamlama kapatma modelini kullandığından, gözlemcileri manuel olarak kaldırmamız gerekmez token(kapsamın dışına çıktığında , gözlemci bizim için kaldırılır) veya superanahtar, eşleşme. Kapatma sadece bu gözlemci çağrıldığında çağrılır. Daha fazla bilgi için WWDC 2017 videosu, Foundation'daki Yenilikler başlıklı videoya bakın .

Swift 3'te bunu gözlemlemek biraz daha karmaşık, ama Objective-C'de olanlara çok benziyor. Yani, observeValue(forKeyPath keyPath:, of object:, change:, context:)(a) bağlamımızla uğraştığımızdan emin olursunuz ( superörneğimizin gözlemlemek için kaydettiği bir şeyi değil ); ve daha sonra (b) gerektiği şekilde ele alın veya superuygulamaya aktarın. Ve uygun olduğunda kendinizi bir gözlemci olarak çıkardığınızdan emin olun. Örneğin, yeniden konumlandırıldığında gözlemciyi kaldırabilirsiniz:

Swift 3'te:

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

}

Not: Yalnızca Objective-C içinde temsil edilebilecek özellikleri gözlemleyebilirsiniz. Böylece, jenerikleri, Swift structtürlerini, Swift enumtürlerini vb. Gözlemleyemezsiniz .

Swift 2 uygulaması hakkında bir tartışma için, aşağıdaki orijinal cevabıma bakın.


Kullanımı dynamicile Kvo ulaşmak için anahtar kelime NSObjectalt sınıfları açıklanan Anahtar-Değer Gözlem bölümünde benimsenmesi Kakao Tasarım Kuralları bölüm Kakao ve Objective-C ile kullanma Swift rehberi:

Anahtar / değer gözlemleme, nesnelerin diğer nesnelerin belirtilen özelliklerindeki değişikliklerden haberdar edilmesini sağlayan bir mekanizmadır. Sınıf sınıftan miras aldığı sürece bir Swift sınıfıyla anahtar / değer gözlemini kullanabilirsiniz NSObject. Swift'te anahtar / değer gözlemini uygulamak için bu üç adımı kullanabilirsiniz.

  1. dynamicDeğiştiriciyi, gözlemlemek istediğiniz herhangi bir özelliğe ekleyin . Hakkında daha fazla bilgi için dynamicbkz . Dinamik Dağıtım Gereksinimi .

    class MyObjectToObserve: NSObject {
        dynamic var myDate = NSDate()
        func updateDate() {
            myDate = NSDate()
        }
    }
  2. Genel bağlam değişkeni oluşturun.

    private var myContext = 0
  3. Anahtar yolu için bir gözlemci ekleyin ve observeValueForKeyPath:ofObject:change:context:yöntemi geçersiz kılın ve içindeki gözlemciyi kaldırın 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)
        }
    }

[Not: Bu KVO tartışması daha sonra Swift 3 için uyarlanmış Swift'i Kakao ve Objective-C ile Kullanma kılavuzundan kaldırılmıştır , ancak yine de bu cevabın üst kısmında belirtildiği gibi çalışmaktadır.]


Swift'in kendi yerel mülkiyet gözlemleme sistemine sahip olduğunu belirtmek gerekir , ancak bu, kendi özelliklerinin gözlemlenmesi üzerine gerçekleştirilecek kendi kodunu belirten bir sınıf içindir. Öte yandan KVO, başka bir sınıfın bazı dinamik özelliklerindeki değişiklikleri gözlemlemek için kayıt olmak üzere tasarlanmıştır.


Amacı nedir myContextve birden fazla özelliği nasıl gözlemlersiniz?
devth

1
Göre KVO Programlama Kılavuz ", bir gözlemci olarak bir nesne kayıt, aynı zamanda bir sağlayabilir contextişaretçi. contextİşaretçi gözlemciye sağlanır observeValueForKeyPath:ofObject:change:context:çağrılır. contextGösterici bir Cı işaretçi veya bir amacı bir referans olabilir. contextİşaretçi olabilir gözlemlenen değişikliği belirlemek veya gözlemciye başka veriler sağlamak için benzersiz bir tanımlayıcı olarak kullanılır. "
Rob

deinit'teki gözlemciyi kaldırmanız gerekiyor
Jacky

3
@devth, anladığım kadarıyla, alt sınıf veya üst sınıf KVO gözlemcisini aynı değişken için kaydederse, observeValueForKeyPath birden çok kez çağrılır. Bağlam bu durumda kendi bildirimlerini ayırt etmek için kullanılabilir. Bununla
Zmey

1
optionsBoş bırakırsanız , yalnızca changeeski veya yeni değeri içermeyeceği anlamına gelir (örneğin, nesnenin kendisine başvurarak yeni değeri kendiniz alabilirsiniz). Sadece belirtirseniz .newdeğil .old, bu demektir changesadece yeni bir değer, ancak eski değerini (örneğin sık sık eski değer ne umurumda değil, ama sadece yeni değere önem) içerecektir. Eğer gerekiyorsa observeValueForKeyPathsana eski ve yeni değeri hem geçmesine, daha sonra belirtin [.new, .old]. Alt satırda, sözlüğe optionsnelerin dahil edildiğini belirtir change.
Rob

92

Hem evet hem hayır:

  • Evet , Objective-C nesnelerini gözlemlemek için Swift'teki aynı eski KVO API'lerini kullanabilirsiniz.
    Ayrıca dynamic, devralınan Swift nesnelerinin özelliklerini de gözlemleyebilirsiniz NSObject.
    Ama ... Hayır , Swift yerel gözlem sisteminin olmasını beklediğiniz gibi güçlü bir şekilde yazılmış değil.
    Swift'i Kakao ve Objective-C ile Kullanma | Anahtar Değeri Gözlemleme

  • Hayır , şu anda keyfi Swift nesneleri için yerleşik bir değer gözlem sistemi yoktur.

  • Evet , güçlü bir şekilde yazılmış yerleşik Özellik Gözlemcileri vardır.
    Ama ... Hayır , KVO değiller, çünkü sadece nesnelerin kendi özelliklerinin gözlemlenmesine izin verirler, iç içe gözlemleri ("anahtar yollar") desteklemezler ve bunları açıkça uygulamanız gerekir.
    Hızlı Programlama Dili | Mülkiyet Gözlemcileri

  • Evet , güçlü bir şekilde yazılan açık değer gözlemini uygulayabilir ve diğer nesnelerden birden çok işleyici eklemeye izin verebilir ve hatta yuvalama / "anahtar yollarını" destekleyebilirsiniz.
    Ama ... Hayır , sadece gözlemlenebilir olarak uyguladığınız mülkler için çalışacağından KVO olmayacak.
    Böyle bir değeri gözlemlemek için bir kütüphane bulabilirsiniz:
    Observable-Swift - Swift için KVO - Değer Gözlem ve Olaylar


10

Burada bir örnek biraz yardımcı olabilir. Özniteliklere sahip bir modelsınıf Modelörneğim varsa nameve statebu öznitelikleri aşağıdakilerle gözlemleyebilirsem:

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)

Bu özelliklerde yapılan değişiklikler aşağıdakilere yapılan aramayı tetikler:

override func observeValueForKeyPath(keyPath: String!,
    ofObject object: AnyObject!,
    change: NSDictionary!,
    context: CMutableVoidPointer) {

        println("CHANGE OBSERVED: \(change)")
}

2
Yanılmıyorsam, observeValueForKeyPath çağrı yaklaşımı Swift2 içindir.
Aralık'ta Fattie

9

Evet.

KVO dinamik dağıtım gerektirir, bu nedenle dynamicdeğiştiriciyi bir yönteme, özelliğe, aboneliğe veya başlatıcıya eklemeniz yeterlidir :

dynamic var foo = 0

dynamicBeyana referanslar dinamik aracılığıyla gönderilir ve erişilen modifiye edici olmasını sağlar objc_msgSend.


7

Rob'un cevabına ek olarak. Bu sınıf miras almalı NSObjectve mülk değişikliğini tetiklemenin 3 yolu var

Şuradan kullanın setValue(value: AnyObject?, forKey key: String):NSKeyValueCoding

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        setValue(NSDate(), forKey: "myDate")
    }
}

Kullanım willChangeValueForKeyve didChangeValueForKeygelenNSKeyValueObserving

class MyObjectToObserve: NSObject {
    var myDate = NSDate()
    func updateDate() {
        willChangeValueForKey("myDate")
        myDate = NSDate()
        didChangeValueForKey("myDate")
    }
}

Kullanın dynamic. Bkz. Swift Türü Uyumluluğu

Dinamik değiştiriciyi, bir yöntemin uygulanmasını dinamik olarak değiştiren anahtar / değer gözlemleme gibi API'ler kullanıyorsanız üyelere erişimin Objective-C çalışma zamanı üzerinden dinamik olarak gönderilmesini istemek için de kullanabilirsiniz.

class MyObjectToObserve: NSObject {
    dynamic var myDate = NSDate()
    func updateDate() {
        myDate = NSDate()
    }
}

Ve özellik alıcı ve ayarlayıcı kullanıldığında çağrılır. KVO ile çalışırken doğrulayabilirsiniz. Bu, hesaplanan özelliğe bir örnektir

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
        }
    }
}

5

genel bakış

Veya kullanmadan Combinekullanmak mümkündür.NSObjectObjective-C

Kullanılabilirlik: iOS 13.0+ , macOS 10.15+, tvOS 13.0+, watchOS 6.0+, Mac Catalyst 13.0+,Xcode 11.0+

Not: Yalnızca değer türleriyle değil sınıflarla kullanılması gerekir.

Kod:

Hızlı Sürüm: 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

Çıktı:

Option 1: value changed to 10
Option 2: value changed to 10
Option 1: value changed to 20
Option 2: value changed to 20

bakınız:


4

Şu anda Swift, 'benlik' dışındaki nesnelerin özellik değişikliklerini gözlemlemek için yerleşik bir mekanizmayı desteklememektedir, bu nedenle hayır, KVO'yu desteklemez.

Bununla birlikte, KVO, Objective-C ve Cocoa'nın o kadar temel bir parçasıdır ki gelecekte eklenecek gibi görünmektedir. Mevcut belgeler bunu ima ediyor gibi görünüyor:

Anahtar / Değer İzleme

Gelecek bilgiler.

Swift'i Kakao ve Objective-C ile Kullanma


2
Açıkçası, bu referansta artık Swift'te KVO'nun nasıl yapılacağı açıklanmaktadır.
Rob

4
Evet, şimdi Eylül 2014 itibariyle uygulandı
Max MacLeod

4

Söz gereken önemli bir nokta da güncellemeden sonra olmasıdır Xcode için 7 beta aşağıdaki iletiyi alıyor olabilirler: "Yöntem, üst sınıfında yöntemlerden herhangi birini geçersiz kılmaz" . Bunun sebebi, argümanların isteğe bağlılığıdır. Gözlem işleyicinizin tam olarak aşağıdaki gibi olduğundan emin olun:

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

2
Xcode beta 6'da şunları gerektirir: geçersiz kılma func observeValueForKeyPath (keyPath: String ?, ofObject object: AnyObject ?, change: [String: AnyObject] ?, context: UnsafeMutablePointer <Void>)
hcanfly 8:15

4

Bu, az sayıda insana yardımcı olabilir -

// 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'te bu şekilde KVO kullanmıştım. Bu kodu birkaç değişiklikle kullanabilirsiniz.


1

Int? Gibi türlerle ilgili sorun yaşayan herkes için başka bir örnek? ve CGFloat ?. Sınıfınızı NSObject öğesinin bir alt sınıfı olarak ayarlarsınız ve değişkenlerinizi aşağıdaki gibi bildirirsiniz:

class Theme : NSObject{

   dynamic var min_images : Int = 0
   dynamic var moreTextSize : CGFloat = 0.0

   func myMethod(){
       self.setValue(value, forKey: "\(min_images)")
   }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.