2016-06-23 6 views
9

साथ हाल ही में, मैं इस कोड लिखा था इसके बारे में बहुत ज्यादा सोच के बिना:यात्रा के दौरान संग्रह से तत्व निकालें foreach

myObject.myCollection.forEach { myObject.removeItem($0) } 

जहां myObject.removeItem(_)myObject.myCollection से एक आइटम को हटा।

अब कोड को देखकर, मुझे परेशान है कि यह क्यों काम करता है - मुझे Collection was mutated while being enumerated की लाइनों के साथ अपवाद नहीं मिलना चाहिए? नियमित कोड-इन लूप का उपयोग करते समय भी वही कोड काम करता है!

क्या यह अपेक्षित व्यवहार है या मैं भाग्यशाली हूं कि यह क्रैश नहीं हो रहा है?

+1

चेतावनी के शब्द::

यहाँ प्रभाव का प्रदर्शन एक सरल उदाहरण है 'removeItem (_ :)' 'है हे (एन)', के रूप में 'foreach (_ :)' है। यह पंक्ति कुल में 'ओ (एन^2) 'है। सेट अंकगणित का उपयोग कर लायक हो सकता है। – Alexander

+0

यह जरूरी नहीं है कि 'ओ (एन) '- सेट' ओ (1)' हैं। मैं 'removeItem (_ :)' विधि स्वयं (एक त्वरित संग्रह पर ऐसी कोई विधि नहीं है) की आपूर्ति करता हूं, और वहां कुछ अन्य टियरडाउन-सामान करता हूं। –

+1

इसके अलावा, इस संग्रह में आम तौर पर 2-4 आइटम होते हैं, इसलिए मैंने जानबूझकर संभावित रनटाइम हिट लिया;) –

उत्तर

11

यह वास्तव में व्यवहार की उम्मीद है - और इस तथ्य के कारण है कि Array स्विफ्ट में (साथ ही साथ मानक लाइब्रेरी में कई अन्य संग्रह) एक मूल्य प्रकार है जो कॉपी-ऑन-राइट सेमेन्टिक्स के साथ है। इसका मतलब यह है कि इसके अंतर्निहित बफर (जिसे परोक्ष रूप से संग्रहीत किया जाता है) उत्परिवर्तित होने पर कॉपी किया जाएगा (और, अनुकूलन के रूप में, केवल तभी जब इसे विशिष्ट रूप से संदर्भित नहीं किया जाता है)।

(ध्यान दें कि यह जवाब कि पहले कहा कि प्रति इटरेटर जो, के रूप में @MartinR सही ढंग से बताया करने के लिए पारित किया जा रहा पर क्या होगा, क्या नहीं होता है।)

आप एक Sequence से अधिक पुनरावृति करने के लिए आते हैं (जैसे कि सरणी), forEach(_:) या मानक for in लूप के साथ हो, एक इटरेटर अनुक्रम की makeIterator() विधि से बनाया गया है, और यह क्रमशः तत्व उत्पन्न करने के लिए next() विधि को बार-बार लागू किया जाता है।

आप इस तरह लग रही के रूप में एक दृश्य के ऊपर बार-बार दोहराना के बारे में सोच सकते हैं:

let sequence = [1, 2, 3, 4] 
var iterator = sequence.makeIterator() 

// next() will return the next element, or nil if it's reached the end of the sequence. 
while let element = iterator.next() { 
    // do something with the element 
} 

Array के मामले में, के रूप में यह इटरेटर है एक IndexingIterator प्रयोग किया जाता है - जो केवल द्वारा दिए गए संग्रह के तत्वों के माध्यम से पुनरावृति जाएगा पुनरावृत्ति के वर्तमान सूचकांक के साथ उस संग्रह को संग्रहीत संग्रहित करता है। प्रत्येक बार next() कहा जाता है, मूल संग्रह को इंडेक्स के साथ सब्सक्राइब किया जाता है, जिसे तब बढ़ाया जाता है, जब तक यह endIndex तक पहुंचता है (आप इसे exact implementation here देख सकते हैं)।

इसलिए, जब आप लूप में अपनी सरणी को म्यूटेट करने के लिए आते हैं, तो यह अंतर्निहित बफर विशिष्ट रूप से संदर्भित नहीं है, क्योंकि इटरेटर के पास भी इसका दृश्य है। यह बफर की एक प्रति को मजबूर करता है - जो myCollection तब उपयोग करता है।

तो, अब दो सरणी हैं - एक जिसे फिर से चालू किया जा रहा है, और जिसे आप उत्परिवर्तित कर रहे हैं। लूप में कोई और उत्परिवर्तन एक और प्रतिलिपि नहीं ट्रिगर करेगा, जब तक myCollection का बफर विशिष्ट रूप से संदर्भित रहता है।

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

+0

आपके उत्तर के लिए धन्यवाद! मैं 'removeItem (_ :)' विधि स्वयं को आपूर्ति करता हूं (एक त्वरित संग्रह के लिए ऐसी कोई विधि नहीं है, और नाम केवल मेरे पॉइंट को स्पष्ट करने के लिए प्लेसहोल्डर है), और वहां हटाए गए ऑब्जेक्ट के लिए कुछ अन्य टियरडाउन-सामान करें , इसलिए मैं संग्रह पर 'removeAll() 'को बस कॉल नहीं कर सकता। –

+0

@ डेविड आह ठीक है, अब समझ में आता है। मदद करने के लिए खुश :) – Hamish

+0

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

6

मैंने ऐप्पल डेवलपर फोरम में similar question से पूछा और उत्तर "हाँ, ऐरे के मूल्य अर्थशास्त्र के कारण" है।

@ originaluser2 कि पहले ही कहा है, लेकिन मैं थोड़ा अलग तर्क है: जब myObject.removeItem($0) कहा जाता है, एक नई सरणी बनाई गई है और नाम myObject के तहत संग्रहीत है, लेकिन सरणी कि forEach() का आह्वान किया गया था संशोधित नहीं है।

extension Array { 
    func printMe() { 
     print(self) 
    } 
} 

var a = [1, 2, 3] 
let pm = a.printMe // The instance method as a closure. 
a.removeAll() // Modify the variable `a`. 
pm() // Calls the method on the value that it was created with. 
// Output: [1, 2, 3] 
+0

दरअसल, यह मेरा से बहुत स्पष्ट उदाहरण है :) सिद्धांत समान है - 'a.removeAll()' 'a' की एक प्रति बना देगा, सभी तत्वों को हटा देगा, फिर उसे वापस ' A'। 'ए' का मूल मान अप्रभावित है, और आप इसे अपने उदाहरण विधियों में से एक संदर्भ के माध्यम से बनाए रखा जा सकता है, और इसे बाद में प्रदर्शित करने के लिए इसे कॉल कर सकते हैं। – Hamish

+0

@ originaluser2: हां, तर्क में अंतर मामूली है। आपने कहा (अगर मैं इसे सही तरीके से समझता हूं) कि इटरेटर को सरणी की एक प्रति मिलती है। मैं कहता हूं कि मूल सरणी पर इटरेटर ऑपरेटर, और उत्परिवर्तन जो प्रतिलिपि बनाता है। लेकिन जैसा कि आप मंच में मेरे प्रश्न से देख सकते हैं, इसे पाने में मुझे थोड़ी देर लग गई। –

+0

निश्चित रूप से दोनों एक (सैद्धांतिक) प्रतिलिपि बनाते हैं? इटेटर को एक प्रतिलिपि बनाना है क्योंकि इसे मूल सरणी के साथ प्रारंभ किया गया है, जिसे तब एक आंतरिक संपत्ति (मूल्य प्रकारों को नाटक करना चाहिए जो कि एक प्रतिलिपि बनाना चाहिए) - और फिर उत्परिवर्तन हमेशा एक प्रतिलिपि बनायेगा, उत्परिवर्तन करें और फिर फिर से -assign। – Hamish

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