जेनेरिक SwiftUI कंपोनेंट बनाने के लिए, किसी प्रोटोकॉल में उसके इनिशियलाइज़र की आवश्यकता रखें या ऐसा क्लोज़र इंजेक्ट करें जो उसे लौटाए। यह ट्यूटोरियल दोनों तरीकों का उपयोग करके macOS विंडो में एक ही कृत्रिम आइटम दिखाता है, और कंपाइलर तथा SDK की सीमाएँ स्पष्ट रखता है।

जेनेरिक कंपोनेंट को क्या वादा करना चाहिए?

मान लें कि macOS ऐप एक छोटा आइटम कार्ड दिखाता है। आप चाहते हैं कि कोई पुनः उपयोग योग्य पैनल किसी आइटम से कार्ड बनाए, जबकि कॉल करने वाले को उसकी प्रस्तुति चुनने की सुविधा मिले। मुख्य सवाल यह है कि निर्माण की ज़िम्मेदारी किसकी है: कंपोनेंट के घोषित इंटरफ़ेस की, या कंपोनेंट उपलब्ध कराने वाले कॉलर की।

जेनेरिक कंस्ट्रेंट से इम्प्लीमेंटेशन को वही क्षमताएँ मिलती हैं जो उस कंस्ट्रेंट में बताई गई हैं। Component: View लिखने से यह तय होता है कि कंपोनेंट SwiftUI व्यू है; इससे यह तय नहीं होता कि उसमें init(item:) नाम का इनिशियलाइज़र भी है। Swift का Generics अध्याय प्रोटोकॉल कंस्ट्रेंट समझाता है, जबकि Apple का View दस्तावेज़ व्यू की आवश्यक body प्रॉपर्टी का वर्णन करता है।

इसलिए, यह अंश जानबूझकर अमान्य है:

// Invalid fragment: View does not require init(item:).
@MainActor
func construct<Component: View>(item: Item) -> Component {
    Component(item: item)
}

किसी एक ठोस कंपोनेंट में वह इनिशियलाइज़र जोड़ने से वह हर उस जेनेरिक पैरामीटर के लिए उपलब्ध नहीं हो जाता जिस पर केवल View का कंस्ट्रेंट हो। पुनः उपयोग योग्य API को निर्माण का स्पष्ट कॉन्ट्रैक्ट चाहिए। कंपोनेंट बदलने से पहले इस निर्णय तालिका का उपयोग करें:

निर्माण की आवश्यकता उपयुक्त इंटरफ़ेस कारण
हर भाग लेने वाला कंपोनेंट वही आइटम स्वीकार करता है init(item:) की आवश्यकता वाला प्रोटोकॉल निर्माण कंपोनेंट परिवार के इंटरफ़ेस का हिस्सा है।
कॉलर अतिरिक्त कॉन्फ़िगरेशन या मॉडिफ़ायर चुनते हैं जेनेरिक कंपोनेंट लौटाने वाली फ़ैक्टरी क्लोज़र निर्माण कॉल साइट पर रहता है।
कॉलर के पास पहले से तैयार कंपोनेंट है जेनेरिक कंपोनेंट वैल्यू को स्टोर करें निर्माण पहले ही हो चुका है।
प्रस्तुति सीमित संख्या वाले लेआउट्स में बदलती है SwiftUI व्यू के भीतर विकल्पों को संयोजित करें आसपास की API ठोस व्यू संरचना बनाए रख सकती है।

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

macOS का पूरा उदाहरण बनाएँ

SwiftUI और macOS 11 या उसके बाद के डिप्लॉयमेंट लक्ष्य वाला Xcode macOS ऐप टार्गेट इस्तेमाल करें। यह उदाहरण Swift 6 कंपाइलर और उससे मेल खाने वाले Apple SDK के लिए लिखा गया है। इसमें स्पष्ट @MainActor एनोटेशन के लिए Swift 5.5 या उसके बाद का संस्करण चाहिए, इसलिए यह कोड पहले के कंपाइलरों के लिए नहीं है। Apple के दस्तावेज़ बताते हैं कि WindowGroup macOS 11 से उपलब्ध है और ऐप की व्यू हायरार्की के लिए कंटेनर का काम करता है। देखें WindowGroup।

जनरेट की गई ऐप एंट्री-पॉइंट फ़ाइल को इस पूरे प्रोग्राम से बदलें। सुनिश्चित करें कि टार्गेट में केवल एक @main ऐप घोषणा हो। जनरेट की गई ContentView फ़ाइल अप्रयुक्त रह सकती है। किसी पैकेज, बाहरी एसेट या डाउनलोड किए गए नमूना डेटा की ज़रूरत नहीं है।

import SwiftUI

struct Item {
    let title: String
    let detail: String
}

@MainActor
protocol ItemComponent: View {
    init(item: Item)
}

@MainActor
struct ItemCard: ItemComponent {
    let item: Item

    init(item: Item) {
        self.item = item
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(item.title)
                .font(.headline)
            Text(item.detail)
        }
        .padding()
        .background(Color.gray.opacity(0.12))
        .cornerRadius(8)
    }
}

@MainActor
struct ContractPanel<Component: ItemComponent>: View {
    let item: Item

    init(item: Item, component: Component.Type) {
        self.item = item
    }

    var body: some View {
        Component(item: item)
    }
}

@MainActor
struct FactoryPanel<Component: View>: View {
    private let makeComponent: @MainActor () -> Component

    init(make: @escaping @MainActor () -> Component) {
        self.makeComponent = make
    }

    var body: some View {
        makeComponent()
    }
}

@MainActor
struct DemoView: View {
    private let item = Item(
        title: "Sample tile",
        detail: "Blue circle on a neutral background"
    )

    var body: some View {
        VStack(alignment: .leading, spacing: 18) {
            Text("Initializer contract")
            ContractPanel(item: item, component: ItemCard.self)

            Text("Injected factory")
            FactoryPanel {
                ItemCard(item: item)
                    .padding(.leading, 12)
            }

            Text("Alternative factory output")
            FactoryPanel {
                Text(item.title + ": " + item.detail)
            }
        }
        .padding(24)
        .frame(width: 420)
    }
}

@main
@MainActor
struct ComponentDemoApp: App {
    var body: some Scene {
        WindowGroup {
            DemoView()
        }
    }
}

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

मेन एक्टर एनोटेशन UI निर्माण की सीमा को स्पष्ट करते हैं। Apple की मौजूदा View घोषणा पर @MainActor और @preconcurrency लगे हैं; इसके दस्तावेज़ यह भी बताते हैं कि टाइप की मूल घोषणा में conformance होने पर isolation कैसे विरासत में मिलता है। देखें View।

इनिशियलाइज़र कॉन्ट्रैक्ट कैसे काम करता है?

ItemComponent, View को refine करता है और निर्माण की ठीक एक आवश्यकता जोड़ता है: init(item:)। Swift प्रोटोकॉल में सीधे इनिशियलाइज़र की आवश्यकताएँ तय करने देता है। Conforming टाइप इम्प्लीमेंटेशन देता है; प्रोटोकॉल इनिशियलाइज़र की बॉडी के बिना उसका सिग्नेचर बताता है। देखें Protocols: Initializer Requirements।

यह आवश्यकता ContractPanel के भीतर Component(item: item) को वैध बनाती है। component: Component.Type आर्ग्युमेंट जेनेरिक इन्फ़रेंस के लिए एक ठोस टाइप देता है: ItemCard.self पास करने से तय होता है कि इस पैनल का कंपोनेंट टाइप ItemCard है। पैनल को मेटाटाइप स्टोर करने की ज़रूरत नहीं है, क्योंकि उसका body जेनेरिक टाइप पैरामीटर के ज़रिए निर्माण करता है।

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

इसका समझौता तब सामने आता है, जब इम्प्लीमेंटेशन को अतिरिक्त कॉन्फ़िगरेशन चाहिए। init(item:) की आवश्यकता का अर्थ है कि हर conforming कंपोनेंट को निर्माण का यह रास्ता देना होगा, भले ही उसका पसंदीदा इनिशियलाइज़र कैप्शन या लेआउट सेटिंग भी स्वीकार करता हो। अगर यह अतिरिक्त जानकारी कॉलर की ज़िम्मेदारी है, तो नीचे दिया गया फ़ैक्टरी तरीका आम तौर पर आवश्यकता को अधिक सीधे व्यक्त करता है।

क्लास-आधारित इम्प्लीमेंटेशन के लिए, प्रोटोकॉल की आवश्यकता पूरी करने वाला इनिशियलाइज़र required होना चाहिए, जब तक कि क्लास final न हो। जिस सबक्लास में मेल खाता सुपरक्लास designated initializer भी override किया गया हो, उसे required और override दोनों की ज़रूरत पड़ सकती है। ItemCard जैसे स्ट्रक्ट required का उपयोग नहीं करते। ये अंतर Protocols में दर्ज हैं।

निर्माण को फ़ैक्टरी में कब ले जाना चाहिए?

FactoryPanel @MainActor () -> Component प्रकार का फ़ंक्शन स्वीकार करता है। इसका वादा बस इतना है कि फ़ंक्शन को कॉल करने पर चुने गए कंपोनेंट टाइप का परिणाम मिलेगा। क्लोज़र किसी इनिशियलाइज़र को कॉल कर सकता है, कॉन्फ़िगरेशन कैप्चर कर सकता है, या मॉडिफ़ायर लागू करके कोई व्यू लौटा सकता है।

प्रोग्राम व्यावहारिक अंतर दिखाता है: फ़ैक्टरी अतिरिक्त पैडिंग वाला ItemCard लौटा सकती है, या Text लौटा सकती है; इसके लिए इनमें से किसी भी परिणामकारी टाइप को ItemComponent अपनाने की ज़रूरत नहीं होती। Apple बताता है कि व्यू मॉडिफ़ायर अपने इनपुट को किसी दूसरे व्यू में लपेट सकते हैं, इसलिए यहाँ कंपाइलर को फ़ैक्टरी का परिणामी टाइप infer करने देना उपयोगी है। देखें View।

क्लोज़र को बाद में चलाने के लिए पैनल स्टोर करता है, इसलिए इनिशियलाइज़र क्लोज़र पर @escaping लगाता है। Swift का Closures अध्याय escaping क्लोज़र और आसपास की वैल्यूज़ को कैप्चर करने की उनकी क्षमता दर्ज करता है। इस उदाहरण में क्लोज़र आसपास के व्यू द्वारा इस्तेमाल किए गए कृत्रिम आइटम को कैप्चर करता है।

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

@MainActor
struct ItemFactoryPanel<Component: View>: View {
    let item: Item
    private let makeComponent: @MainActor (Item) -> Component

    init(
        item: Item,
        make: @escaping @MainActor (Item) -> Component
    ) {
        self.item = item
        self.makeComponent = make
    }

    var body: some View {
        makeComponent(item)
    }
}

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

क्या फ़ैक्टरियों को रिज़ल्ट बिल्डर चाहिए?

पूरे प्रोग्राम में फ़ैक्टरियाँ साधारण क्लोज़र हैं। हर क्लोज़र एक अभिव्यक्ति लौटाता है, इसलिए किसी कस्टम बिल्डर एनोटेशन की ज़रूरत नहीं है। अगर कई चाइल्ड व्यू चाहिए, तो इस API के लिए एक VStack लौटाना पर्याप्त है; स्टैक में चाइल्ड व्यू शामिल होते हैं।

रिज़ल्ट बिल्डर तब उपयोगी होता है, जब फ़ैक्टरी पैरामीटर को खुद अभिव्यक्तियों के घोषणात्मक क्रम या सशर्त सामग्री को स्वीकार करना हो। Swift Evolution दर्ज करता है कि मानकीकृत रिज़ल्ट-बिल्डर सुविधा Swift 5.4 में लागू हुई। देखें SE-0289: Result builders।

बिल्डर सिंटैक्स अपनाते समय SDK की सीमा बनाए रखें। Apple के मौजूदा दस्तावेज़ Xcode 26 और उससे पहले के संस्करणों में व्यू बनाने वाले पैरामीटर के लिए @ViewBuilder का वर्णन करते हैं। Xcode 27 और बाद के संस्करणों के लिए, वे टाइप-अज्ञेय निर्माण का वर्णन करते हैं और उस व्यवहार को दर्शाने के लिए ContentBuilder alias की सिफ़ारिश करते हैं। देखें ViewBuilder और ContentBuilder। यहाँ इस्तेमाल किया गया साधारण क्लोज़र उस नए alias की आवश्यकता से बचता है।

opaque या existential टाइप कॉन्ट्रैक्ट की जगह क्यों नहीं लेते?

some View एक विशिष्ट अंतर्निहित परिणाम टाइप को छिपाता है, लेकिन उसकी पहचान बनाए रखता है। यह इस प्रोग्राम की body प्रॉपर्टीज़ के लिए अच्छी तरह काम करता है। यह View इंटरफ़ेस में init(item:) नहीं जोड़ता। any View के रूप में लिखा boxed वैल्यू अपने अंतर्निहित टाइप को अलग तरीके से छिपाता है और वही स्थिर टाइप पहचान बनाए नहीं रखता। देखें Opaque and Boxed Protocol Types।

इसे इस व्यापक दावे में न बदलें कि existential वैल्यूज़ को कभी भी जेनेरिक फ़ंक्शनों में पास नहीं किया जा सकता। SE-0352 ने Swift 5.7 में implicitly opened existentials जोड़े, जिससे योग्य कॉल जेनेरिक पैरामीटर को existential के अंतर्निहित टाइप से बाँध सकते हैं। इसमें पाबंदियाँ भी दर्ज हैं, जैसे वे परिणाम जो किसी जेनेरिक wrapper में खोले गए टाइप को समाहित करते हैं, जहाँ मिटाए गए टाइप के बाद परिणाम को दर्शाया नहीं जा सकता। देखें Implicitly Opened Existentials।

इस पैनल के काम के लिए, जेनेरिक पैरामीटर के ज़रिए ठोस कंपोनेंट टाइप बनाए रखना पर्याप्त है। केवल कॉलर को व्यू बनाने का तरीका चुनने देने के लिए existential स्टोरेज जोड़ने की ज़रूरत नहीं है।

निर्माण की त्रुटि का पता कैसे लगाएँ?

विफल हो रही अभिव्यक्ति का पता लगाएँ और उस जगह उपलब्ध कॉन्ट्रैक्ट देखें:

  1. अगर Component(item:) विफल होता है, तो जाँचें कि जेनेरिक कंस्ट्रेंट में वही सटीक इनिशियलाइज़र सिग्नेचर माँगने वाला प्रोटोकॉल शामिल है।
  2. अगर कंपाइलर Component infer नहीं कर पाता, तो ItemCard.self जैसा ठोस मेटाटाइप दें, या ठोस return expression वाली फ़ैक्टरी दें।
  3. अगर किसी कंपोनेंट को कॉलर-विशिष्ट कॉन्फ़िगरेशन चाहिए, तो प्रोटोकॉल में असंबंधित आवश्यकताएँ जोड़ने के बजाय निर्माण को फ़ैक्टरी में ले जाएँ।
  4. अगर क्लोज़र स्टोर करने पर escaping-closure डायग्नोस्टिक आता है, तो इनिशियलाइज़र का @escaping एनोटेशन जाँचें।
  5. अगर निर्माण पर actor-isolation डायग्नोस्टिक आता है, तो जाँचें कि कॉल साइट और फ़ैक्टरी का फ़ंक्शन टाइप, दोनों इच्छित main actor सीमा व्यक्त करते हैं।
  6. अगर existential से जेनेरिक परिणाम की त्रुटि आती है, तो यह मानने से पहले कि कंपाइलर अपग्रेड सीमा हटा देगा, SE-0352 में परिणाम-टाइप संबंधी पाबंदियाँ देखें।

साझे macOS कंपोनेंट परिवार के लिए, जब समान निर्माण इंटरफ़ेस का हिस्सा हो, तब इनिशियलाइज़र आवश्यक रखें। जब कॉलर को प्रस्तुति तैयार और कॉन्फ़िगर करनी हो, तब इंजेक्ट की गई फ़ैक्टरी इस्तेमाल करें। दोनों तरीके निर्माण के छूटे हुए वादे को स्पष्ट करते हैं और साथ ही पैनल के लिए ज़रूरी ठोस कंपोनेंट टाइप बनाए रखते हैं।

स्रोत

  • Protocols — Swift प्रोग्रामिंग भाषा, Swift.org.
  • Generics — Swift प्रोग्रामिंग भाषा, Swift.org.
  • Closures — Swift प्रोग्रामिंग भाषा, Swift.org.
  • Opaque and Boxed Protocol Types — Swift प्रोग्रामिंग भाषा, Swift.org.
  • View — Apple डेवलपर दस्तावेज़।
  • WindowGroup — Apple डेवलपर दस्तावेज़।
  • ViewBuilder — Apple डेवलपर दस्तावेज़।
  • ContentBuilder — Apple डेवलपर दस्तावेज़।
  • Result builders — Swift Evolution.
  • Implicitly Opened Existentials — Swift Evolution.