स्विफ्ट में विलसेट और डिडसेट का उद्देश्य क्या है?


265

स्विफ्ट में C # के समान एक संपत्ति घोषणा सिंटैक्स है:

var foo: Int {
    get { return getFoo() }
    set { setFoo(newValue) }
}

हालाँकि, इसके willSetऔर didSetकार्य भी हैं । इन्हें पहले और बाद में सेटर कहा जाता है, क्रमशः कहा जाता है। उनका उद्देश्य क्या है, यह देखते हुए कि आप सेटर के अंदर एक ही कोड रख सकते हैं?


11
मुझे व्यक्तिगत रूप से यहाँ कई उत्तर पसंद नहीं हैं। वे वाक्य रचना में बहुत नीचे जाते हैं। मतभेद शब्दार्थ और कोड की पठनीयता के बारे में अधिक हैं। कंप्यूटेड प्रॉपर्टी ( get& set) मूल रूप से एक प्रॉपर्टी की गणना दूसरी प्रॉपर्टी के आधार पर की जाती है, जैसे कि एक लेबल textको एक साल में बदलना Int। कहने के लिए didSet& willSetहैं ... हे इस मान को सेट किया गया था, अब यह करते हैं जैसे कि हमारा डेटा स्रोत अपडेट किया गया था ... तो चलिए तालिका दृश्य पुनः लोड करते हैं ताकि इसमें नई पंक्तियाँ शामिल हों। एक अन्य उदाहरण के लिए डेलिग का जवाबdidSet
हनी

जवाबों:


324

यह प्रतीत होता है कि कभी-कभी, आपको ऐसी संपत्ति की आवश्यकता होती है जिसमें स्वचालित भंडारण और कुछ व्यवहार होता है, उदाहरण के लिए अन्य वस्तुओं को सूचित करने के लिए कि संपत्ति बस बदल गई। जब आपके पास सभी get/ है set, तो आपको मान रखने के लिए एक और फ़ील्ड की आवश्यकता है। साथ willSetऔर didSet, आप कार्रवाई कर सकते हैं जब मूल्य किसी अन्य क्षेत्र की जरूरत के बिना संशोधित किया गया है। उदाहरण के लिए, उस उदाहरण में:

class Foo {
    var myProperty: Int = 0 {
        didSet {
            print("The value of myProperty changed from \(oldValue) to \(myProperty)")
        }
    }
}

myPropertyहर बार संशोधित होने के बाद अपने पुराने और नए मूल्य को प्रिंट करता है। बस गेट्स और सेटर्स के साथ, मुझे इसके बजाय इसकी आवश्यकता होगी:

class Foo {
    var myPropertyValue: Int = 0
    var myProperty: Int {
        get { return myPropertyValue }
        set {
            print("The value of myProperty changed from \(myPropertyValue) to \(newValue)")
            myPropertyValue = newValue
        }
    }
}

इसलिए willSetऔर didSetदो लाइनों की अर्थव्यवस्था का प्रतिनिधित्व करते हैं, और क्षेत्र सूची में कम शोर है।


248
ध्यान दें: willSetऔर didSetनहीं कहा जाता है जब आप एक नोट विधि के भीतर से Apple नोट्स के रूप में संपत्ति निर्धारित करते हैं:willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
क्लैस

4
लेकिन वे ऐसा करते समय एक सरणी संपत्ति पर बुलाया जाना लगता है: myArrayProperty.removeAtIndex(myIndex)... अपेक्षित नहीं।
एंड्रियास

4
आप असाइनमेंट को डिफरेंट {} स्टेटमेंट में इनिशियलाइज़र के अंदर लपेट सकते हैं जो विलसेट और डिडसेट मेथड्स का कारण बनता है, जब इनिशियल स्कोप बाहर निकल जाता है। मैं यह जरूरी नहीं कह रहा हूँ, यह कहते हुए कि यह संभव है। परिणामों में से एक यह है कि यह केवल तभी काम करता है यदि आप संपत्ति को वैकल्पिक घोषित करते हैं, क्योंकि यह शुरुआती से सख्ती से शुरू नहीं किया जा रहा है।
मर्मॉय

कृपया नीचे लाइन में बताएं। मुझे नहीं मिल रहा है, क्या यह तरीका या वैरिएबल var प्रोपर्टीचेंगलिस्टनर: (Int, Int) -> Void = {println ("myProperty का मान ($ 0) से ($ 1)") में बदल गया है)
Vikash Rajput

एक ही पंक्ति में प्रारंभिक गुण स्विफ्ट 3 में समर्थित नहीं हैं। आपको स्विफ्ट के अनुरूप उत्तर को बदलना चाहिए 3.
रमजान पोलैट

149

मेरी समझ यह है कि सेट और प्राप्त गणना गुणों के लिए है ( संग्रहीत गुणों से कोई पीछे नहीं )

यदि आप एक उद्देश्य-सी को ध्यान में रखते हुए आ रहे हैं तो नामकरण परंपराएं बदल गई हैं। स्विफ्ट में एक iVar या उदाहरण चर को संग्रहीत संपत्ति कहा जाता है

उदाहरण 1 (केवल संपत्ति पढ़ें) - चेतावनी के साथ:

var test : Int {
    get {
        return test
    }
}

यह एक चेतावनी के परिणामस्वरूप होगा क्योंकि यह एक पुनरावर्ती फ़ंक्शन कॉल (गेट्टर खुद को कॉल करता है) के परिणामस्वरूप होता है। इस मामले में चेतावनी "अपने ही गेट के भीतर 'परीक्षण को संशोधित करने का प्रयास" है।

उदाहरण 2. सशर्त पढ़ना / लिखना - चेतावनी के साथ

var test : Int {
    get {
        return test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        //(prevents same value being set)
        if (aNewValue != test) {
            test = aNewValue
        }
    }
}

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

उदाहरण 3. पढ़ी गई / लिखी गई संपत्ति - बैकिंग स्टोर के साथ

यहां एक पैटर्न है जो वास्तविक संग्रहीत संपत्ति की सशर्त सेटिंग की अनुमति देता है

//True model data
var _test : Int = 0

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

नोट वास्तविक डेटा _test कहलाता है (हालाँकि यह कोई भी डेटा या डेटा का संयोजन हो सकता है) नोट भी एक प्रारंभिक मूल्य प्रदान करने की आवश्यकता है (वैकल्पिक रूप से आपको एक init विधि का उपयोग करने की आवश्यकता है) क्योंकि _test वास्तव में एक उदाहरण चर है

उदाहरण 4. वसीयत का उपयोग करना और सेट करना

//True model data
var _test : Int = 0 {

    //First this
    willSet {
        println("Old value is \(_test), new value is \(newValue)")
    }

    //value is set

    //Finaly this
    didSet {
        println("Old value is \(oldValue), new value is \(_test)")
    }
}

var test : Int {
    get {
        return _test
    }
    set (aNewValue) {
        //I've contrived some condition on which this property can be set
        if (aNewValue != test) {
            _test = aNewValue
        }
    }
}

यहां हम एक वास्तविक संचित संपत्ति में बदलाव को रोकते हुए विलसेट और डिडसेट देखते हैं। सूचनाएं भेजने, सिंक्रनाइज़ेशन आदि के लिए यह उपयोगी है ... (नीचे उदाहरण देखें)

उदाहरण 5. ठोस उदाहरण - ViewController कंटेनर

//Underlying instance variable (would ideally be private)
var _childVC : UIViewController? {
    willSet {
        //REMOVE OLD VC
        println("Property will set")
        if (_childVC != nil) {
            _childVC!.willMoveToParentViewController(nil)
            self.setOverrideTraitCollection(nil, forChildViewController: _childVC)
            _childVC!.view.removeFromSuperview()
            _childVC!.removeFromParentViewController()
        }
        if (newValue) {
            self.addChildViewController(newValue)
        }

    }

    //I can't see a way to 'stop' the value being set to the same controller - hence the computed property

    didSet {
        //ADD NEW VC
        println("Property did set")
        if (_childVC) {
//                var views  = NSDictionaryOfVariableBindings(self.view)    .. NOT YET SUPPORTED (NSDictionary bridging not yet available)

            //Add subviews + constraints
            _childVC!.view.setTranslatesAutoresizingMaskIntoConstraints(false)       //For now - until I add my own constraints
            self.view.addSubview(_childVC!.view)
            let views = ["view" : _childVC!.view] as NSMutableDictionary
            let layoutOpts = NSLayoutFormatOptions(0)
            let lc1 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("|[view]|",  options: layoutOpts, metrics: NSDictionary(), views: views)
            let lc2 : AnyObject[] = NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: layoutOpts, metrics: NSDictionary(), views: views)
            self.view.addConstraints(lc1)
            self.view.addConstraints(lc2)

            //Forward messages to child
            _childVC!.didMoveToParentViewController(self)
        }
    }
}


//Computed property - this is the property that must be used to prevent setting the same value twice
//unless there is another way of doing this?
var childVC : UIViewController? {
    get {
        return _childVC
    }
    set(suggestedVC) {
        if (suggestedVC != _childVC) {
            _childVC = suggestedVC
        }
    }
}

ध्यान दें कि BOTH संगणित और संग्रहीत गुणों का उपयोग करें। मैंने दो बार एक ही मूल्य निर्धारित करने से रोकने के लिए एक संगणक संपत्ति का उपयोग किया है (बुरी चीजों से बचने के लिए!); मैंने ViewControllers के लिए सूचनाओं को अग्रेषित करने के लिए .Set और didSet का उपयोग किया है (देखें UIViewController प्रलेखन और viewController कंटेनरों की जानकारी)

मुझे आशा है कि यह मदद करता है, और कृपया किसी ने चिल्लाना अगर मैंने यहाँ कहीं भी गलती की है!


3
कठबोली 'का उपयोग क्यों मैं getSet एक साथ मिल और सेट का उपयोग करें ..?
बेन सिनक्लेयर

//I can't see a way to 'stop' the value being set to the same controller - hence the computed property चेतावनी के बाद मैं के if let newViewController = _childVC { बजाय इस्तेमाल किया if (_childVC) {
0

5
प्राप्त और सेट का उपयोग गणना की गई संपत्ति बनाने के लिए किया जाता है। ये विशुद्ध रूप से विधियां हैं, और कोई बैकिंग स्टोरेज (उदाहरण चर) नहीं है। willSet और didSet संग्रहीत चर गुणों में परिवर्तन देखने के लिए हैं। हुड के तहत, ये भंडारण द्वारा समर्थित हैं, लेकिन स्विफ्ट में यह सभी एक में पिघल गया है।
user3675131

आपके उदाहरण में 5, में get, मुझे लगता है कि आपको जोड़ने की जरूरत है if _childVC == nil { _childVC = something }और फिर return _childVC
JW.ZG

18

इन्हें संपत्ति पर्यवेक्षक कहा जाता है :

संपत्ति के पर्यवेक्षक किसी संपत्ति के मूल्य में परिवर्तन का निरीक्षण करते हैं और उसका जवाब देते हैं। प्रॉपर्टी ऑब्जर्वर को हर बार प्रॉपर्टी की वैल्यू सेट करने के लिए कहा जाता है, भले ही नया मूल्य प्रॉपर्टी के मौजूदा मूल्य के समान हो।

इसके अंश: Apple Inc. "स्विफ्ट प्रोग्रामिंग लैंग्वेज।" iBooks। https://itun.es/ca/jEUH0.l

मुझे लगता है कि यह उन चीजों के लिए अनुमति देने के लिए है जो हम परंपरागत रूप से KVO के साथ करते हैं जैसे कि UI तत्वों के साथ डेटा बाइंडिंग, या किसी संपत्ति को बदलने के साइड इफेक्ट, ट्रिगर प्रक्रिया, पृष्ठभूमि प्रसंस्करण, आदि को ट्रिगर करना आदि।


16

ध्यान दें

willSetऔर didSetपर्यवेक्षकों जब एक संपत्ति एक प्रारंभकर्ता में सेट किया गया है इससे पहले कि प्रतिनिधिमंडल जगह लेता है कहा जाता है नहीं कर रहे हैं


16

आप didSetवैरिएबल को एक अलग मान पर सेट करने के लिए भी उपयोग कर सकते हैं । इससे पर्यवेक्षक को गुण गाइड में फिर से कहा जाने वाला कारण नहीं बनता है । उदाहरण के लिए, यह उपयोगी है जब आप नीचे दिए गए मान को सीमित करना चाहते हैं:

let minValue = 1

var value = 1 {
    didSet {
        if value < minValue {
            value = minValue
        }
    }
}

value = -10 // value is minValue now.

10

कई अच्छी तरह से लिखे गए मौजूदा उत्तर प्रश्न को अच्छी तरह से कवर करते हैं, लेकिन मैं कुछ विस्तार से उल्लेख करूंगा, एक अतिरिक्त जो मुझे विश्वास है कि कवर करने लायक है।


willSetऔर didSetसंपत्ति पर्यवेक्षकों प्रतिनिधियों, जैसे, वर्ग गुण है कि केवल कभी उपयोगकर्ता बातचीत के द्वारा अद्यतन कर रहे हैं के लिए कॉल करने के लिए इस्तेमाल किया जा सकता है, लेकिन जहां वस्तु प्रारंभ में प्रतिनिधि बुला बचना चाहते हैं।

मैं स्वीकार किए गए उत्तर के लिए कलस अप-वोट की गई टिप्पणी का हवाला दूंगा:

विलसेट और डिडसेट पर्यवेक्षकों को तब नहीं बुलाया जाता है जब किसी संपत्ति को पहली बार शुरू किया जाता है। उन्हें केवल तब कहा जाता है जब संपत्ति का मूल्य एक प्रारंभिक संदर्भ के बाहर सेट किया जाता है।

यह काफी साफ-सुथरा है क्योंकि इसका मतलब यह है कि didSetसंपत्ति प्रतिनिधि कॉलबैक और कार्यों के लिए लॉन्च बिंदु का एक अच्छा विकल्प है, अपने स्वयं के कस्टम वर्गों के लिए।

एक उदाहरण के रूप में, कुछ प्रमुख संपत्ति value(जैसे रेटिंग नियंत्रण में स्थिति) के साथ उप-वर्ग के रूप में कार्यान्वित कुछ कस्टम उपयोगकर्ता नियंत्रण ऑब्जेक्ट पर विचार करें UIView:

// CustomUserControl.swift
protocol CustomUserControlDelegate {
    func didChangeValue(value: Int)
    // func didChangeValue(newValue: Int, oldValue: Int)
    // func didChangeValue(customUserControl: CustomUserControl)
    // ... other more sophisticated delegate functions
}

class CustomUserControl: UIView {

    // Properties
    // ...
    private var value = 0 {
        didSet {
            // Possibly do something ...

            // Call delegate.
            delegate?.didChangeValue(value)
            // delegate?.didChangeValue(value, oldValue: oldValue)
            // delegate?.didChangeValue(self)
        }
    }

    var delegate: CustomUserControlDelegate?

    // Initialization
    required init?(...) { 
        // Initialise something ...

        // E.g. 'value = 1' would not call didSet at this point
    }

    // ... some methods/actions associated with your user control.
}

इसके बाद आपका प्रतिनिधि कार्यों के लिए मॉडल में मुख्य परिवर्तनों को निरीक्षण करने के लिए में, कहते हैं, कुछ दृश्य नियंत्रक इस्तेमाल किया जा सकता CustomViewController, वैसे ही जैसे आप के निहित प्रतिनिधि कार्यों का उपयोग करेंगे UITextFieldDelegateके लिए UITextFieldवस्तुओं (जैसे textFieldDidEndEditing(...))।

इस सरल उदाहरण के लिए, didSetक्लास प्रॉपर्टी से एक प्रतिनिधि कॉलबैक का उपयोग करके valueएक व्यू कंट्रोलर को बताएं कि इसमें से एक आउटलेट से संबंधित मॉडल अपडेट है:

// ViewController.swift
Import UIKit
// ...

class ViewController: UIViewController, CustomUserControlDelegate {

    // Properties
    // ...
    @IBOutlet weak var customUserControl: CustomUserControl!

    override func viewDidLoad() {
        super.viewDidLoad()
        // ...

        // Custom user control, handle through delegate callbacks.
        customUserControl = self
    }

    // ...

    // CustomUserControlDelegate
    func didChangeValue(value: Int) {
        // do some stuff with 'value' ...
    }

    // func didChangeValue(newValue: Int, oldValue: Int) {
        // do some stuff with new as well as old 'value' ...
        // custom transitions? :)
    //}

    //func didChangeValue(customUserControl: CustomUserControl) {
    //    // Do more advanced stuff ...
    //}
}

यहां, valueसंपत्ति का एनकैप्सुलेट किया गया है, लेकिन आम तौर पर: इन स्थितियों में, संबंधित प्रतिनिधि समारोह के कार्यक्षेत्र में ऑब्जेक्ट की valueसंपत्ति को अपडेट नहीं करने के लिए सावधान रहें customUserControl(यहां:) didChangeValue()व्यू कंट्रोलर में, या आप के साथ समाप्त हो जाएगा अनंत पुनरावृत्ति।


4

जब भी संपत्ति को एक नया मूल्य सौंपा जाता है, उसके लिए विलसेट और डिडसेट पर्यवेक्षक होते हैं। यह तब भी सही है जब नया मूल्य वर्तमान मूल्य के समान हो।

और ध्यान दें कि willSetदूसरी ओर, काम करने के लिए एक पैरामीटर नाम की आवश्यकता didSetहोती है।

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


2

गेट्टर और सेटर कभी-कभी केवल उचित मूल्य परिवर्तनों का निरीक्षण करने के लिए लागू करने के लिए बहुत भारी होते हैं। आमतौर पर इसके लिए अतिरिक्त अस्थायी चर हैंडलिंग और अतिरिक्त जांच की आवश्यकता होती है, और यदि आप सैकड़ों गेटर्स और सेटर लिखते हैं, तो आप उन छोटे श्रम से भी बचना चाहेंगे। ये सामान स्थिति के लिए हैं।


1
क्या आप कह रहे हैं कि समकक्ष सेटर कोड का उपयोग करने और बनाम करने के लिए एक प्रदर्शन लाभ है ? यह एक साहसिक दावा की तरह लगता है। willSetdidSet
जंक

1
@zneak मैंने गलत शब्द का इस्तेमाल किया। मैं प्रोग्रामर प्रयास का दावा कर रहा हूं, प्रसंस्करण लागत का नहीं।
इयोनिल

1

अपने स्वयं के (आधार) वर्ग में, willSetऔर didSetकाफी निरर्थक हैं , जैसा कि आप इसके बजाय एक गणना की गई संपत्ति (यानी प्राप्त करें और सेट-विधियाँ) को परिभाषित कर सकते हैं _propertyVariableजो वांछित प्री- और पोस्ट-प्रोसेडिंग का उपयोग करता है

हैं, तथापि , आप एक वर्ग जहां संपत्ति है ओवरराइड पहले से परिभाषित , तोwillSet और didSetकर रहे हैं उपयोगी और अनावश्यक नहीं!


1

didSetजब आप अतिरिक्त कॉन्फ़िगरेशन को जोड़ने के लिए आउटलेट का उपयोग करते हैं तो एक चीज़ वास्तव में बहुत उपयोगी होती है।

@IBOutlet weak var loginOrSignupButton: UIButton! {
  didSet {
        let title = NSLocalizedString("signup_required_button")
        loginOrSignupButton.setTitle(title, for: .normal)
        loginOrSignupButton.setTitle(title, for: .highlighted)
  }

या इसका उपयोग करने से इस आउटलेट के तरीकों पर कुछ प्रभाव पड़ेगा, है ना?
एलिया जूल

-5

मुझे C # नहीं पता है, लेकिन थोड़ा अनुमान के साथ मुझे लगता है कि मैं समझता हूं कि क्या है

foo : int {
    get { return getFoo(); }
    set { setFoo(newValue); }
}

कर देता है। यह स्विफ्ट में आपके पास बहुत समान है, लेकिन यह समान नहीं है: स्विफ्ट में आपके पास getFooऔर नहीं है setFoo। यह थोड़ा अंतर नहीं है: इसका मतलब है कि आपके पास अपने मूल्य के लिए कोई अंतर्निहित भंडारण नहीं है।

स्विफ्ट में संग्रहीत और गणना किए गए गुण हैं।

एक गणना की गई संपत्ति getहो सकती है और हो सकती है set(यदि यह लिखने योग्य है)। लेकिन गेटटर और सेटर में कोड, अगर उन्हें वास्तव में कुछ डेटा स्टोर करने की आवश्यकता है, तो इसे अन्य गुणों में करना होगा । कोई बैकिंग स्टोरेज नहीं है।

दूसरी ओर एक संग्रहीत संपत्ति, बैकिंग भंडारण है। लेकिन यह नहीं है getऔर set। इसके बजाय इसके पास है willSetऔर didSetजिसका उपयोग आप चर परिवर्तन का निरीक्षण करने के लिए कर सकते हैं और अंततः, साइड इफेक्ट ट्रिगर और / या संग्रहीत मूल्य को संशोधित कर सकते हैं। आपके पास willSetऔर didSetगणना किए गए गुणों के लिए नहीं है , और आपको उनकी आवश्यकता नहीं है क्योंकि गणना किए गए गुणों के लिए आप setपरिवर्तनों को नियंत्रित करने के लिए कोड का उपयोग कर सकते हैं ।


यह स्विफ्ट का उदाहरण है। getFooऔर setFooजो कुछ भी आप पाने के लिए चाहते हैं और बसने के लिए सरल प्लेसहोल्डर हैं। C # की उन्हें आवश्यकता नहीं है। (मुझे कंपाइलर तक पहुंचने से पहले मैंने कुछ वाक्यात्मक सूक्ष्मताओं को याद किया।)
zneak

1
ओह ठीक। लेकिन महत्वपूर्ण बिंदु यह है कि एक गणना की गई संपत्ति में अंतर्निहित भंडारण नहीं है। मेरा अन्य उत्तर भी देखें: stackoverflow.com/a/24052566/574590
एनालॉग फाइल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.