स्विफ्ट में दृश्यों को लपेटें

सभी को नमस्कार। आज हम पाठ्यक्रम के लॉन्च की पूर्व संध्या पर तैयार किए गए अनुवाद को साझा करना चाहते हैं “iOS डेवलपर। उन्नत पाठ्यक्रम चलो चलते हैं!



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


इस तरह के एक प्रोटोकॉल का एक उदाहरण सीक्वेंस है, जिसे सभी प्रकार के मानक पुस्तकालयों द्वारा स्वीकार किया जाता है जो कि इसे वर्गीकृत किया जा सकता है, जैसे कि ऐरे, शब्दकोश, सेट, और कई अन्य। इस सप्ताह, आइए देखें कि हम सीक्वेंस को सार्वभौमिक कंटेनरों में कैसे लपेट सकते हैं, जो हमें आसानी से उपयोग होने वाले एपीआई के मूल में विभिन्न एल्गोरिदम को एन्कैप्सलेट करने की अनुमति देगा।


आलसी होने की कला


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


//   ,          ,           . let records = database.records(matching: searchQuery) //     ,       ,      . let folders = folder.subfolders //   ,     ,            . let nodes = node.children 

चूँकि उपरोक्त सभी क्रम किसी न किसी कारण से आलसी हैं, इसलिए हम उन्हें ऐरे (फोल्डर.सुबॉल्डर्स) कहकर एक सरणी में मजबूर नहीं करना चाहेंगे। लेकिन हम अभी भी उन्हें अलग-अलग तरीकों से संशोधित करना और उनके साथ काम करना चाह सकते हैं, तो आइए देखें कि हम एक प्रकार का अनुक्रम आवरण बनाकर ऐसा कैसे कर सकते हैं।


नींव का निर्माण


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


 struct WrappedSequence<Wrapped: Sequence, Element>: Sequence { typealias IteratorFunction = (inout Wrapped.Iterator) -> Element? private let wrapped: Wrapped private let iterator: IteratorFunction init(wrapping wrapped: Wrapped, iterator: @escaping IteratorFunction) { self.wrapped = wrapped self.iterator = iterator } func makeIterator() -> AnyIterator<Element> { var wrappedIterator = wrapped.makeIterator() return AnyIterator { self.iterator(&wrappedIterator) } } } 

जैसा कि आप ऊपर देख सकते हैं, अनुक्रम एक कारखाने के पैटर्न का उपयोग करता है ताकि प्रत्येक अनुक्रम प्रत्येक पुनरावृत्ति के लिए एक नया पुनरावृत्त उदाहरण बनाए - मेकइंटर () विधि का उपयोग कर।


ऊपर, हम AnyIterator प्रकार की मानक लायब्रेरी का उपयोग करते हैं, जो कि प्रकार इरेज़र का एक इटरेटर है जो तत्व मान प्राप्त करने के लिए किसी भी मूल IteratorProtocol कार्यान्वयन का उपयोग कर सकता है। हमारे मामले में, हम अपने IteratorFunction को कॉल करके एक तत्व बनाएंगे, एक तर्क के रूप में लिपटे अनुक्रम के अपने स्वयं के पुनरावृत्ति से गुजरना, और चूंकि यह तर्क इनऑउट के रूप में चिह्नित किया गया है, हम अपने फ़ंक्शन के अंदर बेस इटरेटर को बदल सकते हैं।


चूंकि रैप्डसपेंस एक अनुक्रम भी है, इसलिए हम इससे संबंधित मानक पुस्तकालय की सभी कार्यक्षमता का उपयोग कर सकते हैं, जैसे कि इस पर चलना या मानचित्र का उपयोग करके इसके मूल्यों को बदलना:


 let folderNames = WrappedSequence(wrapping: folders) { iterator in return iterator.next()?.name } for name in folderNames { ... } let uppercasedNames = folderNames.map { $0.uppercased() } 

अब चलिए शुरू करते हैं हमारे नए रैप्ड असेंस के साथ!


उपसर्ग और प्रत्यय


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


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


 extension Sequence { func prefixed( with prefixElements: Element... ) -> WrappedSequence<Self, Element> { var prefixIndex = 0 return WrappedSequence(wrapping: self) { iterator in //        ,   ,   ,   : guard prefixIndex >= prefixElements.count else { let element = prefixElements[prefixIndex] prefixIndex += 1 return element } //           : return iterator.next() } } } 

ऊपर, हम एक पैरामीटर को एक या अधिक तत्वों के संचरण की अनुमति देने के लिए एक चर संख्या वाले तर्कों (इसके प्रकार में) का उपयोग करते हैं।
उसी तरह, हम एक ऐसा तरीका बना सकते हैं, जो दिए गए अनुक्रम के अंत में प्रत्ययों का एक सेट जोड़ता है - पहले आधार अनुक्रम के अपने स्वयं के पुनरावृत्ति को प्रदर्शन करके और फिर प्रत्यय तत्वों पर पुनरावृत्ति करता है:


 extension Sequence { func suffixed( with suffixElements: Element... ) -> WrappedSequence<Self, Element> { var suffixIndex = 0 return WrappedSequence(wrapping: self) { iterator in guard let next = iterator.next() else { //    ,     nil      : guard suffixIndex < suffixElements.count else { return nil } let element = suffixElements[suffixIndex] suffixIndex += 1 return element } return next } } } 

उपर्युक्त दो विधियों के साथ, हम अब उपसर्ग और प्रत्यय जोड़ सकते हैं जो हम चाहते हैं। यहाँ कुछ उदाहरण दिए गए हैं कि हमारे नए एपीआई का उपयोग कैसे किया जा सकता है:


 //      : let allFolders = rootFolder.subfolders.prefixed(with: rootFolder) //       : let messages = inbox.messages.suffixed(with: composer.message) //       ,      : let characters = code.prefixed(with: "{").suffixed(with: "}") 

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


Cegmentatsiya


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


 for (index, current) in list.items.enumerated() { let previous = (index > 0) ? list.items[index - 1] : nil let next = (index < list.items.count - 1) ? list.items[index + 1] : nil ... } 

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


 extension Sequence { typealias Segment = ( previous: Element?, current: Element, next: Element? ) var segmented: WrappedSequence<Self, Segment> { var previous: Element? var current: Element? var endReached = false return WrappedSequence(wrapping: self) { iterator in //        ,      ,   ,        ,     . guard !endReached, let element = current ?? iterator.next() else { return nil } let next = iterator.next() let segment = (previous, element, next) //     ,    ,      : previous = element current = next endReached = (next == nil) return segment } } } 

अब हम उपरोक्त एपीआई का उपयोग किसी भी अनुक्रम के खंडित संस्करण को बनाने के लिए कर सकते हैं, जब भी हमें पुनरावृत्ति करते समय आगे या पीछे देखने की आवश्यकता होती है। उदाहरण के लिए, यहां बताया गया है कि हम किस प्रकार सेगमेंटेशन का उपयोग कर सकते हैं ताकि हम आसानी से निर्धारित कर सकें कि हम सूची के अंत तक पहुँच चुके हैं:


 for segment in list.items.segmented { addTopBorder() addView(for: segment.current) if segment.next == nil { //   ,     addBottomBorder() } } ```swift        ,   .    ,               : ```swift for segment in path.nodes.segmented { let directions = ( enter: segment.previous?.direction(to: segment.current), exit: segment.next.map(segment.current.direction) ) let nodeView = NodeView(directions: directions) nodeView.center = segment.current.position.cgPoint view.addSubview(nodeView) } 

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


प्रत्यावर्तन


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


 extension Sequence { func recursive<S: Sequence>( for keyPath: KeyPath<Element, S> ) -> WrappedSequence<Self, Element> where S.Iterator == Iterator { var parentIterators = [Iterator]() func moveUp() -> (iterator: Iterator, element: Element)? { guard !parentIterators.isEmpty else { return nil } var iterator = parentIterators.removeLast() guard let element = iterator.next() else { //          ,    ,      : return moveUp() } return (iterator, element) } return WrappedSequence(wrapping: self) { iterator in //       ,      ,      : let element = iterator.next() ?? { return moveUp().map { iterator = $0 return $1 } }() //       ,  ,         ,         . if let nested = element?[keyPath: keyPath].makeIterator() { let parent = iterator parentIterators.append(parent) iterator = nested } return element } } } 

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


 let allFolders = folder.subfolders.recursive(for: \.subfolders) for folder in allFolders { try loadContent(from: folder) } 

हम इसका उपयोग पेड़ के सभी नोड्स पर पुनरावृति करने के लिए या डेटाबेस रिकॉर्ड्स के एक सेट को पुनरावृत्ति करने के लिए कर सकते हैं - उदाहरण के लिए, किसी संगठन के सभी उपयोगकर्ता समूहों को सूचीबद्ध करने के लिए:


 let allNodes = tree.recursive(for: \.children) let allGroups = database.groups.recusive(for: \.subgroups) 

जब हम पुनरावर्ती पुनरावृत्तियों की बात करते हैं तो एक बात हमें सावधान रहने की जरूरत है - जब एक निश्चित मार्ग हमें एक ऐसे तत्व की ओर लौटाता है जिसका सामना हम पहले ही कर चुके हैं - जो हमें एक अनंत पाश की ओर ले जाएगा।
इसे ठीक करने का एक तरीका यह है कि सभी घटित तत्वों पर नज़र रखी जाए (लेकिन यह मेमोरी समस्याग्रस्त हो सकती है), यह सुनिश्चित करने के लिए कि हमारे डेटा सेट में कोई परिपत्र संदर्भ नहीं हैं, या कॉल साइड से हर बार ऐसे मामलों को संभालने के लिए (ब्रेक का उपयोग करते हुए, जारी रखें या पूरा करने के लिए वापस लौटें। चक्रीय पुनरावृत्तियों)।


निष्कर्ष


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


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

Source: https://habr.com/ru/post/hi464995/


All Articles