क्या स्विफ्ट में कुंजी-मूल्य अवलोकन (KVO) उपलब्ध है?


174

यदि हां, तो क्या कोई महत्वपूर्ण अंतर हैं जो अन्यथा उद्देश्य-सी में कुंजी-मूल्य अवलोकन का उपयोग करते समय अन्यथा मौजूद नहीं थे?


2
एक उदाहरण परियोजना, जो स्विफ्ट के माध्यम से UIKit इंटरफ़ेस में उपयोग किए जा रहे KVO को प्रदर्शित करती है: github.com/jameswomack/kvo-in-swift
james_womack

@JDDvorak KVO प्रोग्रामिंग गाइड देखें , जो विषय का एक अच्छा परिचय है।
रोब

1
यद्यपि आपके प्रश्न का उत्तर नहीं है, आप डिडसेट () फ़ंक्शन का उपयोग करके भी कार्रवाई शुरू कर सकते हैं।
विंसेंट

ध्यान दें कि जब आप उपयोग करते हैं तो एक स्विफ्ट 4 बग होता है .initial। समाधान के लिए यहां देखें । मैं अत्यधिक Apple डॉक्स देखने की सलाह देता हूं । इसे हाल ही में अपडेट किया गया है और इसमें कई महत्वपूर्ण नोट्स शामिल हैं। इसके अलावा रोब का अन्य उत्तर देखें
हनी

जवाबों:


107

(नई जानकारी जोड़ने के लिए संपादित): इस बात पर विचार करें कि क्या केवीओ का उपयोग करने के बजाय कंबाइन फ्रेमवर्क का उपयोग करने से आप जो चाहते थे उसे पूरा करने में मदद कर सकते हैं

हां और ना। KVO NSObject उपवर्गों पर काम करता है, क्योंकि यह हमेशा होता है। यह उन वर्गों के लिए काम नहीं करता है जो NSObject को उप-वर्ग नहीं करते हैं। स्विफ्ट (वर्तमान में कम से कम) की अपनी मूल अवलोकन प्रणाली नहीं है।

(अन्य संपत्तियों को उजागर करने के तरीके के बारे में टिप्पणियां देखें क्योंकि केवीओ उन पर काम करता है)

एक पूर्ण उदाहरण के लिए Apple दस्तावेज़ीकरण देखें ।


74
Xcode 6 बीटा 5 के बाद से आप dynamicKVO समर्थन को सक्षम करने के लिए किसी भी स्विफ्ट क्लास पर कीवर्ड का उपयोग कर सकते हैं ।
fabb

7
हुर्रे @fabb के लिए! स्पष्टता के लिए, dynamicकीवर्ड उस संपत्ति पर जाता है जिसे आप कुंजी-मूल्य-अवलोकन करना चाहते हैं।
जेरी


6
चूँकि यह मेरे लिए @ फैब की टिप्पणी से स्पष्ट नहीं था: dynamicकिसी भी वर्ग के अंदर किसी भी गुण के लिए कीवर्ड का उपयोग करें जिसे आप KVO के अनुरूप होना चाहते हैं ( dynamicवर्ग पर कीवर्ड नहीं )। यह मेरे लिए काम किया!
टिम कैम्बर

1
ज़रुरी नहीं; आप "बाहर" से एक नया डिडसेट पंजीकृत नहीं कर सकते, यह संकलन के समय उस प्रकार का हिस्सा होना चाहिए।
कैटफ़िश_मन

155

आप स्विफ्ट में केवीओ का उपयोग कर सकते हैं, लेकिन केवल उपवर्ग के dynamicगुणों के लिए NSObject। विचार करें कि आप barएक Fooवर्ग की संपत्ति का निरीक्षण करना चाहते थे । स्विफ्ट 4 में, अपने उपवर्ग में संपत्ति के barरूप dynamicमें निर्दिष्ट करें NSObject:

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

फिर आप barसंपत्ति में परिवर्तन देखने के लिए पंजीकरण कर सकते हैं। स्विफ्ट 4 और स्विफ्ट 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)")
        }
    }
}

ध्यान दें, स्विफ्ट 4 में, अब हमारे पास बैकस्लैश कैरेक्टर का उपयोग करके कीपैथ्स का मजबूत टाइपिंग है ( ऑब्जेक्ट \.barकी barसंपत्ति के लिए कीपाथ मनाया जा रहा है)। इसके अलावा, क्योंकि यह समापन समापन पैटर्न का उपयोग कर रहा है, इसलिए हमें पर्यवेक्षकों को मैन्युअल रूप से हटाने की जरूरत नहीं है (जब tokenगुंजाइश से बाहर हो जाता है, पर्यवेक्षक हमारे लिए हटा दिया जाता है) और न ही हमें superकुंजी को लागू करने के लिए कॉल करने के बारे में चिंता करने की ज़रूरत नहीं है मेल खाते हैं। इस विशेष पर्यवेक्षक का आह्वान करने पर ही इसे बंद किया जाता है। अधिक जानकारी के लिए, डब्लूडब्लूडीसी २०१ WW वीडियो देखें, व्हाट्स न्यू इन फाउंडेशन

स्विफ्ट 3 में, इसे देखने के लिए, यह थोड़ा अधिक जटिल है, लेकिन उद्देश्य-सी में जो कुछ करता है, उसके समान है। अर्थात्, आप लागू करेंगे observeValue(forKeyPath keyPath:, of object:, change:, context:)जो (ए) सुनिश्चित करता है कि हम अपने संदर्भ से निपट रहे हैं (और ऐसा कुछ नहीं जिसे हमारे superउदाहरण ने देखने के लिए पंजीकृत किया था); और फिर (बी) इसे या तो संभाल लें या इसे पास कर देंsuper लागू करें, जैसा कि आवश्यक हो। और उपयुक्त होने पर एक पर्यवेक्षक के रूप में खुद को निकालना सुनिश्चित करें। उदाहरण के लिए, जब आप निपटाया जाता है तो आप पर्यवेक्षक को हटा सकते हैं:

स्विफ्ट 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])")
    }

}

ध्यान दें, आप केवल उन गुणों का निरीक्षण कर सकते हैं जिन्हें उद्देश्य-सी में दर्शाया जा सकता है। इस प्रकार, आप जेनरिक, स्विफ्ट structप्रकार, स्विफ्ट का निरीक्षण नहीं कर सकतेenum प्रकार आदि का ।

स्विफ्ट 2 के कार्यान्वयन की चर्चा के लिए, नीचे मेरा मूल उत्तर देखें।


का उपयोग करते हुए dynamicKVO को प्राप्त करने के साथ कीवर्ड NSObjectउपवर्गों में वर्णन किया गया कुंजी-मान अवलोकन की धारा अपनाने कोको डिजाइन सम्मेलनों के अध्याय कोको और ऑब्जेक्टिव-सी के साथ का उपयोग करते हुए स्विफ्ट गाइड:

की-वैल्यू ऑब्जर्विंग एक ऐसा मैकेनिज्म है जो ऑब्जेक्ट्स को अन्य ऑब्जेक्ट्स के निर्दिष्ट गुणों में परिवर्तन के बारे में सूचित करने की अनुमति देता है। जब तक वर्ग वर्ग से विरासत में मिलता है, तब तक आप स्विफ्ट क्लास के साथ अवलोकन कर सकते हैं NSObject। स्विफ्ट में अवलोकन करने के लिए आप इन तीन चरणों का उपयोग कर सकते हैं।

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

[ध्यान दें, इस केवीओ चर्चा को बाद में कोको और ऑब्जेक्टिव-सी गाइड के साथ यूजिंग स्विफ्ट से हटा दिया गया है , जिसे स्विफ्ट 3 के लिए अनुकूलित किया गया है, लेकिन यह अभी भी इस उत्तर के शीर्ष पर उल्लिखित के रूप में काम करता है।]


यह ध्यान देने योग्य है कि स्विफ्ट की अपनी मूल संपत्ति पर्यवेक्षक प्रणाली है, लेकिन यह एक वर्ग के लिए अपने स्वयं के कोड को निर्दिष्ट करता है जो अपने गुणों के अवलोकन पर किया जाएगा। दूसरी ओर, केवीओ को कुछ अन्य वर्ग की कुछ गतिशील संपत्ति में परिवर्तन देखने के लिए पंजीकृत करने के लिए डिज़ाइन किया गया है।


का उद्देश्य क्या है myContextऔर आप कई गुणों का पालन कैसे करते हैं?
देव

1
KVO प्रोग्रामिंग गाइड के अनुसार : "जब आप एक ऑब्जर्वर के रूप में एक ऑब्जेक्ट रजिस्टर करते हैं, तो आप एक contextपॉइंटर भी प्रदान कर सकते हैं । contextपॉइंटर को जब ऑब्जर्वर को भेजा जाता है, तो उसे प्रदान observeValueForKeyPath:ofObject:change:context:किया जाता है। contextपॉइंटर एक सी पॉइंटर या ऑब्जेक्ट रेफरेंस contextहो सकता है । पॉइंटर हो सकता है। एक विशिष्ट पहचानकर्ता के रूप में उपयोग किए जाने वाले परिवर्तन को निर्धारित करने के लिए या पर्यवेक्षक को कुछ अन्य डेटा प्रदान करने के लिए उपयोग किया जाता है। "
रोब

आपको डिनीट में प्रेक्षक को हटाने की आवश्यकता है
जैकी

3
@ देव, जैसा कि मैं समझता हूं, यदि उपवर्ग या सुपरक्लास भी एक ही चर के लिए केवीओ पर्यवेक्षक को पंजीकृत करते हैं, तो ओवरव्यूफोरकेयपाथ को कई बार कहा जाएगा। इस स्थिति में स्वयं की सूचनाओं को अलग करने के लिए संदर्भ का उपयोग किया जा सकता है। इस पर अधिक: dribin.org/dave/blog/archives/2008/09/24/proper_kvo_usage
Zmey

1
यदि आप optionsखाली छोड़ देते हैं , तो इसका मतलब है कि changeइसमें पुराने या नए मूल्य शामिल नहीं होंगे (उदाहरण के लिए आप स्वयं वस्तु को संदर्भित करके नया मान प्राप्त कर सकते हैं)। यदि आप केवल निर्दिष्ट करते हैं .newऔर नहीं करते हैं .old, तो इसका मतलब है कि changeइसमें केवल नया मूल्य शामिल होगा, लेकिन पुराना मूल्य नहीं है (जैसे कि आप अक्सर इस बात की परवाह नहीं करते हैं कि पुराना मूल्य क्या था, लेकिन केवल नए मूल्य के बारे में परवाह है)। यदि आपको observeValueForKeyPathपुराने और नए मूल्य दोनों को पास करने की आवश्यकता है, तो निर्दिष्ट करें [.new, .old]। निचला रेखा, optionsकेवल वही निर्दिष्ट करता है जो changeशब्दकोश में शामिल है ।
रोब

92

दोनों हाँ और नहीं:

  • हां , आप ऑब्जेक्ट-सी ऑब्जेक्ट्स का निरीक्षण करने के लिए स्विफ्ट में एक ही पुराने केवीओ एपीआई का उपयोग कर सकते हैं।
    आप dynamicस्विफ्ट वस्तुओं के गुणों का भी निरीक्षण कर सकते हैं, जो विरासत में मिली हैं NSObject
    लेकिन ... नहीं यह दृढ़ता से टाइप नहीं किया गया है क्योंकि आप उम्मीद कर सकते हैं कि स्विफ्ट देशी अवलोकन प्रणाली हो।
    कोको और उद्देश्य-सी के साथ स्विफ्ट का उपयोग करना | मुख्य मान

  • नहीं , वर्तमान में मनमाने ढंग से स्विफ्ट वस्तुओं के लिए कोई बिलियन मूल्य अवलोकन प्रणाली नहीं है।

  • हां , बिलियन प्रॉपर्टी ऑब्जर्वर हैं , जो दृढ़ता से टाइप किए जाते हैं।
    लेकिन ... नहीं, वे केवीओ नहीं हैं, क्योंकि वे केवल वस्तुओं के स्वयं के गुणों को देखने के लिए अनुमति देते हैं, नेस्टेड टिप्पणियों ("कुंजी पथ") का समर्थन नहीं करते हैं, और आपको उन्हें स्पष्ट रूप से लागू करना होगा।
    स्विफ्ट प्रोग्रामिंग भाषा | संपत्ति पर्यवेक्षकों

  • हां , आप स्पष्ट मान को लागू कर सकते हैं, जो दृढ़ता से टाइप किया जाएगा, और अन्य ऑब्जेक्ट्स से कई हैंडलर जोड़ने की अनुमति देगा, और यहां तक ​​कि नेस्टिंग / "कुंजी पथ" का भी समर्थन करेगा।
    लेकिन ... नहीं, यह केवीओ नहीं होगा क्योंकि यह केवल उन संपत्तियों के लिए काम करेगा जिन्हें आप अवलोकन के रूप में लागू करते हैं।
    आप इस तरह के मूल्य को देखने के लिए एक पुस्तकालय पा सकते हैं:
    अवलोकन-स्विफ्ट - स्विफ्ट के लिए केवीओ - मूल्य अवलोकन और घटनाक्रम


10

एक उदाहरण यहाँ थोड़ी मदद कर सकता है। यदि मेरे पास विशेषताओं के साथ modelवर्ग का एक उदाहरण है और मैं उन विशेषताओं का अवलोकन कर सकता हूं:Modelnamestate

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

2
यदि मैं गलत नहीं हूँ, तो Swift2 के लिए ObsValueForKeyPath कॉल अप्रोच है।
फेटी

9

हाँ।

KVO को गतिशील प्रेषण की आवश्यकता होती है, इसलिए आपको बस dynamicसंशोधक को एक विधि, प्रॉपर्टी, सबस्क्रिप्ट या इनिशलाइज़र में जोड़ना होगा :

dynamic var foo = 0

dynamicसंशोधक सुनिश्चित है कि घोषणा के लिए संदर्भ गतिशील भेजा जाएगा और पहुँचा के माध्यम से objc_msgSend


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स्विफ्ट प्रकार संगतता देखें

आप डायनामिक संशोधक का उपयोग करने के लिए यह भी आवश्यक कर सकते हैं कि यदि आप एपीआई जैसे कुंजी-मूल्य का उपयोग कर रहे हैं तो गतिशील तरीके से ऑब्जेक्टिव-सी रनटाइम के माध्यम से सदस्यों को एक्सेस भेजा जा सकता है।

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

और प्रॉपर्टी गेटर और सेटर का उपयोग करने पर कहा जाता है। केवीओ के साथ काम करने पर आप सत्यापित कर सकते हैं। यह गणना की गई संपत्ति का एक उदाहरण है

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

अवलोकन

इसका उपयोग 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

देखें:


4

वर्तमान में स्विफ्ट 'स्व' के अलावा अन्य वस्तुओं के संपत्ति परिवर्तनों के अवलोकन के लिए किसी भी निर्मित तंत्र का समर्थन नहीं करता है, इसलिए नहीं, यह केवीओ का समर्थन नहीं करता है।

हालांकि, KVO ऑब्जेक्टिव-सी और कोको का ऐसा मौलिक हिस्सा है कि यह काफी संभावना है कि भविष्य में इसे जोड़ा जाएगा। वर्तमान प्रलेखन का यह अर्थ लगता है:

की-वैल्यू का अवलोकन

आगामी जानकारी

कोको और उद्देश्य-सी के साथ स्विफ्ट का उपयोग करना


2
जाहिर है, उस गाइड जो आप संदर्भ अब वर्णन करते हैं कि स्विफ्ट में केवीओ कैसे करें।
रोब

4
हां, अब सितंबर 2014 के रूप में लागू किया गया
मैक्स मैकलेओड

4

उल्लेख करने के लिए एक महत्वपूर्ण बात यह है कि आपके एक्सकोड को 7 बीटा में अपडेट करने के बाद आपको निम्न संदेश प्राप्त हो सकता है: "विधि अपने सुपरक्लास से किसी भी विधि को ओवरराइड नहीं करती है" । ऐसा तर्क के विकल्प के कारण है। सुनिश्चित करें कि आपका अवलोकन हैंडलर इस प्रकार दिखता है:

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

2
Xcode बीटा 6 में इसकी आवश्यकता होती है: ओवरराइड फंक ओवरव्यूएफ़ोरकेपीपैथ (की -पैथ: स्ट्रिंग ?, ऑबजेक्ट ऑब्जेक्ट: AnyObject ?, परिवर्तन: [स्ट्रिंग: AnyObject] ?, संदर्भ: UnsafeMutablePointer <Void>)
hcanfly

4

यह कुछ लोगों के लिए मददगार साबित हो सकता है -

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

मैंने स्विफ्ट 3 में इस तरह केवीओ का उपयोग किया था। आप कुछ बदलावों के साथ इस कोड का उपयोग कर सकते हैं।


1

किसी और के लिए एक और उदाहरण जो इंट जैसे प्रकारों के साथ एक समस्या में चलता है? और 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)")
   }

}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.