एक macOS SwiftUI स्क्रीन में अलग-अलग टाइप्ड कंप्लीशन पाने के लिए, हर कंपोनेंट को जेनेरिक अडैप्टर या टाइप्ड क्लोज़र दें। Swift स्क्रीन को अलग-अलग एसोसिएटेड टाइप के साथ एक ही प्रोटोकॉल के अनुरूप दो बार होने की अनुमति नहीं देता।

यह ट्यूटोरियल एक छोटा डेस्कटॉप प्रीव्यू बनाता है, जिसके बटन एक सिंथेटिक शीर्षक और कॉलम की संख्या लागू करते हैं। उद्देश्य यह है कि कंपाइलर हर कंपोनेंट के कंप्लीशन कॉन्ट्रैक्ट की जाँच करे और एक पैरेंट व्यू को दोनों मान मिल सकें।

एक टाइप दोनों कंप्लीशन कॉन्ट्रैक्ट क्यों नहीं अपना सकता?

किसी प्रोटोकॉल का एसोसिएटेड टाइप उसकी अनुरूपता से जुड़ा होता है। अगर कंप्लीशन प्रोटोकॉल Value नाम का एसोसिएटेड टाइप घोषित करता है, तो अनुरूप होने वाले टाइप को उस टाइप के लिए एक सुसंगत विकल्प देना होगा। दो ओवरलोडेड कंप्लीशन मेथड जोड़ने से दो स्वतंत्र अनुरूपताएँ नहीं बनतीं। Swift की भाषा मार्गदर्शिका बताती है कि प्रोटोकॉल अपनाने पर एसोसिएटेड टाइप अपने ठोस टाइप कैसे प्राप्त करते हैं। जेनेरिक्स — एसोसिएटेड टाइप।

उदाहरण के लिए, कोई स्क्रीन एक बार Value को TitleChoice और फिर दोबारा LayoutChoice के बराबर रखकर एक ही कंप्लीशन प्रोटोकॉल नहीं अपना सकती। घोषणाओं को अलग-अलग एक्सटेंशन में ले जाने से यह पाबंदी नहीं बदलती। SE-0143 दोहराई गई अनुरूपता पर रोक को स्पष्ट रूप से बनाए रखता है, जिसमें ऐसी सशर्त अनुरूपताएँ भी शामिल हैं जिनकी शर्तें अलग-अलग हों। सशर्त अनुरूपताएँ।

प्राइमरी एसोसिएटेड टाइप इस सीमा को नहीं हटाते। CompletionHandling<TitleChoice> सिंटैक्स एसोसिएटेड टाइप पर एक बाधा व्यक्त करता है; यह हर आर्ग्युमेंट के लिए अलग प्रोटोकॉल पहचान नहीं बनाता। SE-0346 ने Swift 5.7 में यह बाधा सिंटैक्स शुरू किया। प्राइमरी एसोसिएटेड टाइप के लिए हल्की same-type आवश्यकताएँ।

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

आपके कंपोनेंट के लिए कौन-सा कंप्लीशन डिज़ाइन सही है?

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

कंपोनेंट की आवश्यकता सुझाया गया डिज़ाइन क्या स्पष्ट रहता है
एक कॉलबैक, जिसका इंटरफ़ेस आपके नियंत्रण में है टाइप्ड क्लोज़र कॉलबैक का इनपुट टाइप
प्रोटोकॉल-आधारित मौजूदा API अनुरूप होने वाला जेनेरिक अडैप्टर प्रोटोकॉल और उसका एसोसिएटेड टाइप
कई नामित विकल्पों वाला एक इवेंट चैनल एसोसिएटेड वैल्यू वाला enum रिसीवर जिन इवेंट केस को संभालता है
अलग-अलग अर्थों वाले कई असंबंधित ऑपरेशन अलग-अलग नाम वाले प्रोटोकॉल हर ऑपरेशन की विशिष्ट आवश्यकताएँ

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

जेनेरिक कंप्लीशन मेथड एक अलग वादा व्यक्त करेगा। complete<T>(_ value: T) जैसे मेथड को उसकी शर्तों से अनुमत हर टाइप के लिए काम करना होगा। यह उस सीमित आवश्यकता को व्यक्त नहीं करता कि हर कंपोनेंट एक खास परिणाम टाइप देता है। Swift का जेनेरिक्स दस्तावेज़ बताता है कि जेनेरिक फ़ंक्शन कॉल साइट पर चुने गए टाइप पर कैसे काम करते हैं। जेनेरिक्स — जेनेरिक फ़ंक्शन।

टाइप्ड कंप्लीशन अडैप्टर बनाएँ

इस उदाहरण के लिए macOS SwiftUI ऐप टार्गेट, Swift 5.7 या उसके बाद का कंपाइलर और macOS 12 या उसके बाद का डिप्लॉयमेंट टार्गेट इस्तेमाल करें। कंपाइलर की यह आवश्यकता प्राइमरी एसोसिएटेड टाइप की घोषणा के कारण है। डिप्लॉयमेंट टार्गेट इस ट्यूटोरियल के लिए एक सावधानीपूर्ण आधार है; यह दावा नहीं कि यहाँ दिखाई गई हर API पहली बार macOS 12 में आई थी।

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

CompletionContracts.swift बनाएँ और ये घोषणाएँ जोड़ें:

import SwiftUI

struct TitleChoice {
    let text: String
}

struct LayoutChoice {
    let columns: Int
}

@MainActor
protocol CompletionHandling<Value> {
    associatedtype Value
    func complete(_ value: Value)
}

@MainActor
struct CompletionAdapter<Output>: CompletionHandling {
    typealias Value = Output

    private let receive: @MainActor (Output) -> Void

    init(_ receive: @escaping @MainActor (Output) -> Void) {
        self.receive = receive
    }

    func complete(_ value: Output) {
        receive(value)
    }
}

@MainActor
struct CompletionButton<Value, Handler: CompletionHandling>: View
where Handler.Value == Value {
    let title: String
    let value: Value
    let handler: Handler

    var body: some View {
        Button(action: {
            handler.complete(value)
        }) {
            Text(title)
        }
    }
}

Handler.Value == Value बाधा कंपोनेंट के आउटपुट को हैंडलर के स्वीकार्य इनपुट से जोड़ती है। इसलिए TitleChoice वाला बटन ऐसे हैंडलर की माँग करता है जिसका एसोसिएटेड टाइप TitleChoice हो। स्पष्ट टाइप एलियस से अडैप्टर का एसोसिएटेड टाइप आसानी से देखा जा सकता है।

अडैप्टर अपने क्लोज़र को बाद में चलाने के लिए रखता है, इसलिए उसका इनिशियलाइज़र उस पैरामीटर को @escaping चिह्नित करता है। Swift का क्लोज़र दस्तावेज़ इस एनोटेशन की माँग करता है, जब कोई क्लोज़र उसे प्राप्त करने वाले फ़ंक्शन कॉल से आगे तक जीवित रह सकता है। क्लोज़र — एस्केपिंग क्लोज़र।

प्रोटोकॉल, अडैप्टर और व्यू @MainActor का उपयोग करते हैं, और रखा गया कॉलबैक अपने फ़ंक्शन टाइप में यही आइसोलेशन बनाए रखता है। इससे UI का अपेक्षित निष्पादन संदर्भ कॉन्ट्रैक्ट का हिस्सा बनता है। SE-0316 ग्लोबल एक्टर आइसोलेशन और ग्लोबल एक्टर क्वालिफ़ाइड फ़ंक्शन टाइप, दोनों का दस्तावेज़ देता है। ग्लोबल एक्टर।

Apple Button को एक एक्शन और एक लेबल लेने वाला बताता है। यहाँ एक्शन कंपोनेंट के पहले से टाइप किए गए मान के साथ हैंडलर को बुलाता है; Text(title) उसका दिखाई देने वाला लेबल देता है। बटन।

दो परिणाम टाइप को एक SwiftUI स्क्रीन से जोड़ें

ContentView.swift बनाएँ। शीर्षक और लेआउट परिणामों के लिए अलग-अलग स्टेट डेस्टिनेशन हैं, और हर अडैप्टर क्लोज़र केवल अपने संबंधित डेस्टिनेशन को अपडेट करता है।

import SwiftUI

@MainActor
struct ContentView: View {
    @State private var previewTitle = "Sample Board"
    @State private var columnCount = 1

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Component Preview")
                .font(.headline)

            Text(previewTitle)
            Text("Columns: \(columnCount)")

            HStack {
                CompletionButton(
                    title: "Apply title",
                    value: TitleChoice(text: "Pebble Board"),
                    handler: CompletionAdapter<TitleChoice> { choice in
                        previewTitle = choice.text
                    }
                )

                CompletionButton(
                    title: "Apply layout",
                    value: LayoutChoice(columns: 3),
                    handler: CompletionAdapter<LayoutChoice> { choice in
                        columnCount = choice.columns
                    }
                )
            }
        }
        .padding(24)
        .frame(minWidth: 420, minHeight: 180)
    }
}

ऐप के एकमात्र एंट्री पॉइंट के रूप में TypedCompletionsApp.swift बनाएँ:

import SwiftUI

@main
@MainActor
struct TypedCompletionsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

पैरेंट अपनी शुरुआती वैल्यू निजी @State प्रॉपर्टी में रखता है। Apple दस्तावेज़ करता है कि SwiftUI इस स्टोरेज को प्रबंधित करता है और वैल्यू बदलने पर निर्भर व्यू अपडेट करता है। मौजूदा दस्तावेज़ यह भी बताता है कि Xcode 27 या उसके बाद के संस्करण से बने बिल्ड में State मैक्रो इस्तेमाल होता है; यहाँ दिखाई गई घोषणा-शैली पहले के प्रॉपर्टी-रैपर आधार का अनुसरण करती है। स्टेट।

WindowGroup स्क्रीन के व्यू पदानुक्रम को होस्ट करता है। Apple दस्तावेज़ करता है कि समूह से बनी हर विंडो का स्टेट स्टोरेज स्वतंत्र होता है, इसलिए इस उदाहरण का स्टेट हर प्रीव्यू विंडो का अपना है। WindowGroup।

यहाँ दो विशेषीकृत अडैप्टर टाइप हैं: CompletionAdapter<TitleChoice> और CompletionAdapter<LayoutChoice>। दोनों एक ही जेनेरिक अडैप्टर घोषणा से बनते हैं। ContentView केवल View के अनुरूप है; यह दिए गए क्लोज़र के माध्यम से दोनों परिणाम प्राप्त करता है।

व्यवहार की जाँच करें और टाइप मिसमैच का निदान करें

रिसर्च वातावरण में Swift कंपाइलर इंस्टॉल नहीं है, इसलिए इन फ़ाइलों का निरीक्षण किया गया, लेकिन इन्हें कंपाइल या रन नहीं किया गया। अपने Mac पर ऐप टार्गेट बनाएँ, फिर उदाहरण के अपेक्षित व्यवहार की जाँच के लिए ये चरण अपनाएँ।

  1. ऐप लॉन्च करें। प्रीव्यू में Sample Board और Columns: 1 दिखना चाहिए।
  2. Apply title पर क्लिक करें। शीर्षक Pebble Board हो जाना चाहिए, जबकि कॉलम की संख्या अपरिवर्तित रहे।
  3. Apply layout पर क्लिक करें। संख्या Columns: 3 हो जानी चाहिए, जबकि चुना गया शीर्षक अपरिवर्तित रहे।
  4. किसी भी बटन पर फिर से क्लिक करें। उसे वही सिंथेटिक मान दोबारा लागू करना चाहिए।

टाइप सीमा देखने के लिए, पहले बटन के हैंडलर स्पेशलाइज़ेशन को अस्थायी रूप से CompletionAdapter<LayoutChoice> में बदलें और उसके क्लोज़र का बॉडी खाली रखें। उस बटन की वैल्यू TitleChoice ही रहने दें। तब कंपोनेंट Handler.Value == Value के माध्यम से असंगत टाइप को समान होने की माँग करता है, इसलिए कंपाइलेशन विफल होना चाहिए। इसके बाद मूल हैंडलर बहाल करें।

अगर कंपाइलर दोहराई गई अनुरूपता की रिपोर्ट करे, तो ऐसी दूसरी घोषणा ढूँढें जो उसी नाममात्र टाइप को CompletionHandling के अनुरूप बनाती है। एकमात्र जेनेरिक अडैप्टर अनुरूपता बनाए रखें और अतिरिक्त आउटपुट के लिए एक और अडैप्टर मान बनाएँ। एक ही अनुरूपता घोषित करने वाले दो सीमित एक्सटेंशन भी Swift की दोहराई गई अनुरूपता संबंधी पाबंदी के अधीन रहते हैं। सशर्त अनुरूपताएँ — कई अनुरूपताएँ।

अगर कंपाइलर प्रोटोकॉल के प्राइमरी एसोसिएटेड टाइप सिंटैक्स को अस्वीकार करे, तो कंपाइलर का संस्करण जाँचें। पुराने कंपाइलर के लिए अडैप्टर डिज़ाइन में सामान्य एसोसिएटेड टाइप घोषणा रखी जा सकती है। यह केवल प्रोटोकॉल घोषणा का प्रतिस्थापन अंश है; बाकी घोषणाएँ वैसी ही रखें:

@MainActor
protocol CompletionHandling {
    associatedtype Value
    func complete(_ value: Value)
}

इस बदलाव से उदाहरण की Swift 5.7 सिंटैक्स पर निर्भरता हटती है। इससे Swift 5.5 में शुरू हुए @MainActor की कंपाइलर आवश्यकता नहीं हटती और न ही ट्यूटोरियल का डिप्लॉयमेंट टार्गेट बदलता है। कंपोनेंट पहले से जेनेरिक where क्लॉज़ का उपयोग करता है, इसलिए उपयोग वाली जगह पर उसे प्राइमरी एसोसिएटेड टाइप सिंटैक्स की ज़रूरत नहीं है। हल्की same-type आवश्यकताएँ, ग्लोबल एक्टर।

यह कंप्लीशन कॉन्ट्रैक्ट क्या गारंटी देता है?

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

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

इस डेस्कटॉप कार्य के लिए हर टाइप्ड एंडपॉइंट पर एक अडैप्टर रखें और पैरेंट को प्राप्त स्टेट का मालिक बनने दें। इससे एक पुनः उपयोग योग्य अनुरूपता घोषणा के माध्यम से दोनों कंपोनेंट को सटीक कंप्लीशन कॉन्ट्रैक्ट मिलते हैं और हर परिणाम का डेस्टिनेशन कॉल साइट पर दिखता है।

स्रोत