2015-05-24 13 views
11

मुझे lazy कीवर्ड के साथ एक संपत्ति का उपयोग करने के बहुत अजीब मामले मिले हैं। मुझे पता है कि यह कीवर्ड इंगित करता है कि किसी प्रॉपर्टी का प्रारंभिक रूप से उपयोग किए जाने वाले चर को तब तक प्रस्तुत किया जाता है जब एक बार चलाता है।क्या यह सामान्य है कि आलसी var संपत्ति दो बार शुरू की जाती है?

हालांकि, मुझे एक प्रारंभिकरण दो बार चलने वाला मामला मिला।

class TestLazyViewController: UIViewController { 

    var name: String = "" { 
     didSet { 
      NSLog("name self = \(self)") 
      testLabel.text = name 
     } 
    } 

    lazy var testLabel: UILabel = { 
     NSLog("testLabel self = \(self)") 
     let label = UILabel() 
     label.text = "hello" 
     self.view.addSubview(label) 
     return label 
    }() 

    override func viewDidLoad() { 
     super.viewDidLoad() 
     testLabel.setTranslatesAutoresizingMaskIntoConstraints(false) 
     NSLayoutConstraint.activateConstraints([NSLayoutConstraint(item: testLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1.0, constant: 0.0)]) 
     NSLayoutConstraint.activateConstraints([NSLayoutConstraint(item: testLabel, attribute: .CenterY, relatedBy: .Equal, toItem: self.view, attribute: .CenterY, multiplier: 1.0, constant: 0.0)]) 
    } 

    @IBAction func testButton(sender: AnyObject) { 
     testLabel.text = "world" 
    } 
} 

मैंने एक परीक्षण के लिए एक व्यू कंट्रोलर लिखा था। यह दृश्य नियंत्रक एक और दृश्य नियंत्रक द्वारा प्रस्तुत किया जाता है। फिर, name प्रॉपर्टी को वर्तमान दृश्य नियंत्रक के prepareForSegue में सेट किया गया है।

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
    let vc = segue.destinationViewController as! TestLazyViewController 
    println("vc = \(vc)") 
    vc.name = "hello" 
} 

परीक्षण चलाने के बाद, मैं निम्नलिखित परिणाम प्राप्त करने में सक्षम था।

vc = <testLazy.TestLazyViewController: 0x7fb3d1d16ec0> 
2015-05-25 00:26:15.673 testLazy[95577:22267122] name self = <testLazy.TestLazyViewController: 0x7fb3d1d16ec0> 
2015-05-25 00:26:15.673 testLazy[95577:22267122] testLabel self = <testLazy.TestLazyViewController: 0x7fb3d1d16ec0> 
2015-05-25 00:26:15.674 testLazy[95577:22267122] testLabel self = <testLazy.TestLazyViewController: 0x7fb3d1d16ec0> 

जैसा कि आप देख सकते हैं, प्रारंभिक कोड दो बार निष्पादित किया जाता है। मुझे नहीं पता कि यह एक बग या दुरुपयोग है। क्या कोई है जो मुझे बताता है कि क्या गलत है?

मुझे यह भी अनुमान है कि यह सही नहीं है कि testLabel को प्रारंभिक कोड में self.view में जोड़ा गया है। मुझे यकीन नहीं है कि कोड गलत है। यह सिर्फ मेरा अनुमान है।

अद्यतन:
मैं अभी भी समझ में नहीं आता क्यों आलसी आरंभीकरण दो बार चलाता है। क्या यह वास्तव में स्विफ्ट की बग है?

अंतिम अद्यतन:
@matt इस समस्या के लिए एक उत्कृष्ट स्पष्टीकरण दो बार प्रारंभ किया जा रहा बना दिया है। हालांकि सभी चीजें मेरे गलत कोड से आती हैं, लेकिन मुझे मूल्यवान ज्ञान मिल सकता है कि कैसे lazy कीवर्ड काम करता है। धन्यवाद मैट।

+2

"मुझे यह भी अनुमान है कि यह सही नहीं है कि प्रारंभिक कोड में testLabel को स्वयं में देखा गया है। यह गलत है। और यह भयानक दुष्प्रभाव पैदा कर रहा है, क्योंकि आप जल्द ही 'व्यू' बना रहे हैं। केवल प्रारंभिककर्ता को प्रमुख साइड इफेक्ट्स न जोड़ें! बस लेबल बनाएं और इसे वापस करें। इसे 'viewDidLoad' में एक सबव्यूव के रूप में जोड़ें। – matt

+0

मैं और जानकारी के साथ तैयार हूं, लेकिन मुझे अभी भी यह नहीं पता कि स्विफ्ट की बग या आपकी बग को कॉल करना है या नहीं। मुझे इसे अपने उत्तर में जोड़ने दें। – matt

+0

ठीक है, सभी सेट। मेरा मानना ​​है कि मैंने इस मुद्दे को पूरी तरह से समझाया है। मैंने यह तय नहीं किया है कि इसे स्विफ्ट बग कहें या नहीं; यह निश्चित रूप से एक बढ़त मामला है, लेकिन यह एक किनारा है जिसे आपको पहले स्थान पर कभी संपर्क नहीं करना चाहिए था। :) फिर भी, आपको एक बग रिपोर्ट के रूप में ऐप्पल को एक टेस्ट प्रोजेक्ट सबमिट करने में उचित ठहराया जाएगा; वे इसके बारे में जानना चाहेंगे। – matt

उत्तर

19

आपके कोड की पूरी अवधारणा गलत है।

  • prepareForSegue में, आप, इंटरफ़ेस गंतव्य दृश्य नियंत्रक के का उल्लेख नहीं होना चाहिए क्योंकि यह कोई इंटरफ़ेस है। viewDidLoad अभी तक नहीं चला है; दृश्य नियंत्रक के पास कोई दृश्य नहीं है, कोई आउटलेट नहीं, कुछ भी नहीं।

  • लेबल संपत्ति के लिए आपके आलसी प्रारंभकर्ता को इंटरफ़ेस में लेबल भी नहीं जोड़ना चाहिए। इसे सिर्फ लेबल बनाना चाहिए और इसे वापस करना चाहिए।

अन्य बातें:

  • का जिक्र करते हुए एक दृश्य नियंत्रक के view इससे पहले कि यह एक दृश्य के समय से पहले ही लोड करने के लिए उस दृश्य के लिए बाध्य करेगा है। यह गलत करने से वास्तव में को दो बार लोड करने का कारण हो सकता है, जिसके परिणामस्वरूप भयानक परिणाम हो सकते हैं।

  • एक दृश्य नियंत्रक से पूछने का एकमात्र तरीका है कि क्या दृश्य अभी तक लोड हो गया है, दृश्य को समय-समय पर लोड करने के लिए मजबूर किए बिना, isViewLoaded() के साथ है।

कि आप क्या करना चाहते हैं के लिए सही प्रक्रिया है:

  • prepareForSegue में, एक name संपत्ति के नाम स्ट्रिंग निर्दिष्ट करेंगे और इतना ही है। इसमें पर्यवेक्षक हो सकते हैं, लेकिन उस पर्यवेक्षक को view का संदर्भ नहीं देना चाहिए यदि हमारे पास उस समय view नहीं है, क्योंकि ऐसा करने से view समय-समय पर लोड हो जाएगा।

  • viewDidLoad में, तब केवल तभी हमारे पास एक दृश्य है, और अब आप इंटरफ़ेस को पॉप्युलेट करना शुरू कर सकते हैं। viewDidLoad लेबल बनाना चाहिए, इसे इंटरफ़ेस में डालें, फिर name प्रॉपर्टी चुनें, और इसे लेबल पर असाइन करें।


संपादित:

अब, कहा है कि सभी ... यह क्या अपने मूल प्रश्न से कोई लेना देना है? आप यहां क्या गलत कर रहे हैं यह बताते हैं कि स्विफ्ट क्या कर रहा है, और स्विफ्ट क्या गलत कर रहा है?

जवाब देखने के लिए, बस पर एक ब्रेकपाइंट डाल:

lazy var testLabel: UILabel = { 
    NSLog("testLabel self = \(self)") // breakpoint here 
    // ... 

आप क्या देखेंगे है कि, क्योंकि जिस तरह से आप अपने कोड संरचित की, हम दो बार testLabel का मूल्य हो रही है रिकर्सिवली । यहाँ कॉल स्टैक, थोड़ा सरलीकृत है:

prepareForSegue 
name.didset 
testLabel.getter -> * 
viewDidLoad 
testLabel.getter -> * 

testLabel गेटर को संदर्भित करता है दृश्य नियंत्रक के view, जिसकी वजह से दृश्य नियंत्रक के दृश्य लोड करने के लिए, और इसलिए इसकी viewDidLoad कहा जाता है और testLabel गेटर फिर कहा जाने का कारण बनता है ।

ध्यान दें कि गेटर को अनुक्रम में केवल दो बार नहीं कहा जा रहा है। इसे दो बार दोहराया जा रहा है: यह स्वयं ही स्वयं को बुला रहा है।

यह रिकर्सन है कि स्विफ्ट के खिलाफ बचाव करने में असफल रहा है। अगर सेटर को उत्तराधिकार में केवल दो बार बुलाया जाता था, तो आलसी प्रारंभकर्ता को दूसरी बार नहीं कहा जाता था। लेकिन आपके मामले में, यह रिकर्सिव है। तो यह सच है कि दूसरी बार, आलसी प्रारंभकर्ता से पहले कभी नहीं चलाया गया है। यह शुरू किया गया है, लेकिन यह कभी भी पूरा नहीं हुआ है। इस प्रकार, स्विफ्ट को अब इसे चलाने में उचित है - जिसका मतलब यह है कि इसे फिर से चलाना है।

तो, एक अर्थ में, हाँ, आपने स्विफ्ट को अपने पैंट के नीचे पकड़ा है, लेकिन ऐसा करने के लिए आपको क्या करना है, यह इतना अपमानजनक है कि इसे उचित रूप से अपनी गलती कहा जा सकता है। यह स्विफ्ट की बग हो सकती है, लेकिन यदि हां, तो यह एक बग है जिसे वास्तविक जीवन में कभी सामना नहीं करना चाहिए।


संपादित करें:

स्विफ्ट और संगामिति पर WWDC 2016 वीडियो में, एप्पल स्पष्ट इस बारे में है। स्विफ्ट 1 और 2 में, और स्विफ्ट 3 में भी, lazy आवृत्ति चर परमाणु नहीं हैं, और इस प्रकार प्रारंभकर्ता एक साथ दो संदर्भों से बुलाए जाने पर दो बार चला सकता है - जो वास्तव में आपका कोड करता है।

+0

मुझे लगता है कि आपका उत्तर उत्कृष्ट है! वास्तव में, मुझे लगता है कि आप क्या इंगित करते हैं। धन्यवाद। लेकिन, मुझे अभी भी अस्पष्ट बात है कि आलसी प्रारंभिक दो बार क्यों चल रहा है। @matt –

+0

हां, मैंने अभी आपकी सलाह लागू करने की कोशिश की और समस्या हल हो गई। एक प्रारंभिकता अब दो बार नहीं चलती है। –

+0

इस पर और जानकारी देने के लिए धन्यवाद। क्या आप मुझे देखे गए वीडियो का शीर्षक बता सकते हैं? –

संबंधित मुद्दे