एक ही डेलीगेट को कई SwiftUI घटक प्रकारों को संभालने देने के लिए, कॉलबैक विधि को जेनेरिक बनाएँ और घटक को डेलीगेट के ठोस प्रकार से स्वतंत्र रखें। यह गाइड एक छोटा macOS टाइल चयनकर्ता बनाती है, जो पुनरावर्ती Self आवश्यकता के बिना भेजने वाले का सामग्री प्रकार बनाए रखता है।
सिंटैक्स चुनने से पहले कॉलबैक अनुबंध चुनें
पाठक का कार्य स्पष्ट है: एक पुनः उपयोग योग्य SwiftUI घटक बनाएँ, जिसकी सामग्री बदल सकती हो, जबकि एक डेलीगेट हर समर्थित सामग्री विशेषीकरण से चयन कॉलबैक प्राप्त करे। प्रदर्शन में दो काल्पनिक टाइलें हैं—पाठ और एक वृत्त—और चयनित टाइल का पहचानकर्ता दिखाया जाता है।
Swift में डेलीगेशन को प्रोटोकॉल-आधारित तरीके के रूप में परिभाषित किया गया है, जिससे कोई क्लास या संरचना अपनी ज़िम्मेदारियाँ किसी अन्य इंस्टेंस को सौंप सकती है। भाषा मार्गदर्शिका के डेलीगेशन उदाहरण में दिखाए अनुसार, डेलीगेट किसी कार्रवाई के साथ उसका भेजने वाला भी प्राप्त कर सकता है। प्रोटोकॉल
इस घटक के लिए अपेक्षित अनुबंध है: “View के अनुरूप हर सामग्री प्रकार के लिए, डेलीगेट उस सामग्री वाली टाइल प्राप्त कर सकता है।” प्रकार पैरामीटर को कॉलबैक विधि पर रखें। Swift के जेनेरिक फ़ंक्शन हर कॉल पर अपने ठोस प्रकार तर्क चुनते हैं, जबकि कोई संबद्ध प्रकार प्रोटोकॉल अनुरूपता के समय तय होता है। ये अलग-अलग प्रतिबद्धताएँ हैं। जेनेरिक
| आवश्यकता | उपयुक्त इंटरफ़ेस रूप | परिणाम |
|---|---|---|
| एक डेलीगेट सभी समर्थित सामग्री प्रकारों को संभालता है | जेनेरिक कॉलबैक विधि | हर कॉलबैक का सामग्री प्रकार अलग हो सकता है। |
| हर डेलीगेट एक निश्चित सामग्री प्रकार संभालता है | संबद्ध प्रकार | हर अनुरूपता एक सामग्री प्रकार देती है। |
| कॉलबैक को केवल टाइल पहचानकर्ता चाहिए | पहचानकर्ता लेने वाला कॉलबैक | कॉलबैक का रेंडरिंग प्रकार पर कोई निर्भरता नहीं होती। |
यह तालिका इन भाषा नियमों से निकला एक डिज़ाइन-सहायक है। जब टाइपयुक्त भेजने वाले को बनाए रखना API का हिस्सा हो, तो पहली पंक्ति चुनें। जब भेजने वाले का पहचानकर्ता प्राप्तकर्ता की हर ज़रूरत पूरी करता हो, तो तीसरी पंक्ति चुनें; तब पूरा घटक साथ ले जाने से अनावश्यक निर्भरता जुड़ जाएगी।
डेलीगेट के अपने प्रकार पर निर्भरता हटाएँ
कसकर जुड़ा हुआ डिज़ाइन घटक को डेलीगेट के लिए जेनेरिक बना सकता है और फिर डेलीगेट प्रोटोकॉल के भीतर उस घटक को Component<Self> के रूप में संदर्भित कर सकता है। यहाँ Self का अर्थ है अंततः अनुरूप होने वाला डेलीगेट प्रकार। संबद्ध प्रकार भी इसी अनुरूप होने वाले प्रकार से जुड़े होते हैं। घोषणाएँ
इस संरचना में कॉलबैक की जाँच ऐसी घटक विशेषीकरण पर निर्भर होती है, जो स्वयं डेलीगेट पर निर्भर है। किसी विशिष्ट पुनरावर्ती घोषणा को स्वीकार किया जाएगा या नहीं, यह उसकी पूरी बाधाओं और कंपाइलर के निदान की जाँच करके ही तय किया जा सकता है। यह कहना गलत होगा कि Swift हर पुनरावर्ती प्रोटोकॉल संबंध को अस्वीकार करता है: SE-0157 ने Swift 4.1 में पुनरावर्ती संबद्ध-प्रकार बाधाएँ जोड़ी थीं। संबद्ध प्रकारों पर पुनरावर्ती बाधाओं का समर्थन करें
चुने गए कार्य के लिए, डेलीगेट का ठोस प्रकार घटक के प्रकार में शामिल होने की आवश्यकता नहीं है। नीचे के डिज़ाइन में Tile का एक जेनेरिक पैरामीटर Content है। इसका संग्रहीत डेलीगेट any TileDelegate है, और प्रोटोकॉल की आवश्यकता अपना विधि-स्तरीय Content पैरामीटर पेश करती है। कॉलबैक, डेलीगेट के Self के ज़रिये व्यक्त किए बिना भेजने वाले का सामग्री प्रकार बनाए रखता है।
any लिखावट स्पष्ट रूप से एक existential मान दर्शाती है: संग्रहीत मान में अनुरूप डेलीगेट प्रकार का इंस्टेंस हो सकता है। SE-0335 ने यह लिखावट Swift 5.6 में प्रस्तुत की थी। डेलीगेट के लिए existential इस्तेमाल करने से टाइल की सामग्री को AnyView में मिटाना आवश्यक नहीं होता। एक्सिस्टेंशियल any प्रस्तुत करें
macOS टाइल चयनकर्ता पूरा बनाएँ
Swift 6 कंपाइलर वाला macOS SwiftUI ऐप टार्गेट इस्तेमाल करें, जैसे Xcode 16 या उसके बाद के संस्करण के साथ मिलने वाला कंपाइलर। इस उदाहरण में @StateObject स्टोरेज के लिए macOS डिप्लॉयमेंट टार्गेट 11.0 या बाद का रखें। Apple के दस्तावेज़ के अनुसार, StateObject macOS 11.0 से उपलब्ध है। StateObject
जनरेट की गई ऐप घोषणा और शुरुआती व्यू को इस एक स्रोत फ़ाइल से बदलें। सुनिश्चित करें कि टार्गेट में केवल एक @main ऐप घोषणा हो; Swift केवल एक प्रोग्राम प्रवेश-बिंदु तरीका स्वीकार करता है। घोषणाएँ
import SwiftUI
import Combine
@MainActor
protocol TileDelegate: AnyObject {
func didChoose<Content: View>(_ tile: Tile<Content>)
}
@MainActor
struct Tile<Content: View>: View {
let id: String
let delegate: any TileDelegate
let content: Content
init(
id: String,
delegate: any TileDelegate,
@ViewBuilder content: () -> Content
) {
self.id = id
self.delegate = delegate
self.content = content()
}
var body: some View {
Button(action: {
delegate.didChoose(self)
}) {
VStack(spacing: 8) {
content
.frame(width: 100, height: 50)
Text("Choose \(id)")
}
.padding(8)
}
}
}
@MainActor
final class SelectionModel: ObservableObject, TileDelegate {
@Published private(set) var selection = "No tile selected"
func didChoose<Content: View>(_ tile: Tile<Content>) {
selection = "Selected: \(tile.id)"
}
}
@MainActor
struct ChooserView: View {
@StateObject private var model = SelectionModel()
var body: some View {
VStack(spacing: 16) {
HStack(spacing: 16) {
Tile(id: "text", delegate: model) {
Text("Sample")
}
Tile(id: "circle", delegate: model) {
Circle()
}
}
Text(model.selection)
}
.padding(24)
.frame(minWidth: 340, minHeight: 180)
}
}
@main
@MainActor
struct DelegateDemoApp: App {
var body: some Scene {
WindowGroup {
ChooserView()
}
}
}
यह एक पूरा ऐप उदाहरण है, जिसमें इम्पोर्ट, डेलीगेट, घटक, मॉडल और प्रवेश-बिंदु शामिल हैं। यहाँ इसे देखा गया, लेकिन कंपाइल या चलाया नहीं गया, क्योंकि उपलब्ध वातावरण में Swift कंपाइलर या macOS SDK नहीं है।
Apple का Button API एक कार्रवाई और व्यू बनाने वाला लेबल स्वीकार करता है। इस उदाहरण में, टाइल पर क्लिक करने से बटन कार्रवाई के भीतर डेलीगेट बुलाया जाता है; body का मूल्यांकन इंटरफ़ेस बनाता है, चयन इवेंट नहीं भेजता। Button
ऐप को Mac पर बनाएँ और चलाएँ। शुरुआत में स्थिति No tile selected दिखनी चाहिए। Choose text पर क्लिक करने से Selected: text दिखना चाहिए; Choose circle पर क्लिक करने से Selected: circle दिखना चाहिए। ये उदाहरण के अपेक्षित परिणाम हैं, दर्ज किए गए निष्पादन परिणाम नहीं।
समझें कि उदाहरण क्या बनाए रखता है
पहली टाइल का प्रकार Tile<Text> है और दूसरी का Tile<Circle>। दोनों एक ही SelectionModel इंस्टेंस का उपयोग करती हैं। इसका जेनेरिक कॉलबैक किसी भी सामग्री प्रकार के लिए प्रोटोकॉल आवश्यकता पूरी करता है, और साझा id प्रॉपर्टी पढ़ता है। प्रकार पैरामीटर को विधि पर रखने का यही व्यावहारिक लाभ है।
प्राप्तकर्ता tile.content को Content के रूप में भी इस्तेमाल कर सकता है। हालाँकि, जेनेरिक कार्यान्वयन के भीतर घोषित बाधा केवल View की क्षमताओं की गारंटी देती है। टेक्स्ट-विशिष्ट कार्रवाई के लिए अलग अनुबंध या अतिरिक्त जानकारी चाहिए होगी; कॉलबैक यह नहीं मान सकता कि हर सामग्री मान Text है। यह Swift के बाधा-आधारित जेनेरिक मॉडल से निकलता है। जेनेरिक
घटक body को some View के साथ लागू करता है और Apple के दस्तावेज़ित कस्टम-व्यू पैटर्न का अनुसरण करता है। मौजूदा View घोषणा में मुख्य-एक्टर आइसोलेशन भी है। उदाहरण UI से जुड़े प्रोटोकॉल और प्रकारों को स्पष्ट रूप से @MainActor चिह्नित करता है, ताकि कॉलबैक और मॉडल अपडेट एक ही आइसोलेशन साझा करें। View
@StateObject में समाहित व्यू की पहचान के पूरे जीवनकाल के लिए स्थिर मॉडल स्टोरेज देता है। मॉडल के प्रकाशित चयन में बदलाव से उस पर निर्भर स्थिति व्यू अपडेट होता है। मॉडल निर्माण को किसी सामान्य कंप्यूटेड एक्सप्रेशन में ले जाने से StateObject के ज़रिये यह स्वामित्व व्यक्त नहीं होगा। StateObject
उदाहरण आवश्यक डेलीगेट को मज़बूत संदर्भ के रूप में संग्रहीत करता है। इसका मॉडल केवल चयन स्ट्रिंग संग्रहीत करता है, इसलिए दिखाए गए ऑब्जेक्ट ग्राफ़ में टाइल की ओर लौटने वाला कोई संदर्भ नहीं है। अगर बाद में मॉडल भेजने वालों को अपने पास रखता है, तो स्वामित्व पर फिर विचार करें। Apple की भाषा मार्गदर्शिका ऐसे संबंध में कमज़ोर स्टोरेज वाले क्लास-बाउंड डेलीगेट का उदाहरण देती है। प्रोटोकॉल
संबद्ध प्रकार बेहतर अनुबंध कब होता है
मान लें कि प्राप्तकर्ता को केवल टेक्स्ट टाइलें स्वीकार करनी चाहिए। एक वैकल्पिक प्रोटोकॉल associatedtype Content: View घोषित कर सकता है और func didChoose(_ tile: Tile<Content>) की आवश्यकता रख सकता है। इससे हर अनुरूपता के लिए एक सामग्री प्रकार तय होता है। Content को प्राथमिक संबद्ध प्रकार बनाने से बाधा लिखने का सिंटैक्स सुविधाजनक होता है; इससे आवश्यकता ऐसी विधि नहीं बन जाती जो हर सामग्री प्रकार स्वीकार करे। SE-0346 ने Swift 5.7 में प्राथमिक संबद्ध प्रकार प्रस्तुत किए थे। प्राथमिक संबद्ध प्रकारों के लिए हल्की समान-प्रकार आवश्यकताएँ
यह विकल्प एक अलग API संस्करण होगा। घटक की डेलीगेट प्रॉपर्टी और इनिशियलाइज़र पैरामीटर को भी मेल खाने वाले सीमित existential, जैसे any FixedTileDelegate<Content>, में बदलना होगा; साथ ही, हर प्राप्तकर्ता को अपना निश्चित संबद्ध प्रकार और उससे मेल खाता गैर-जेनेरिक कॉलबैक देना होगा। केवल टेक्स्ट वाला अनुरूपण उसी अनुरूपता के ज़रिये वृत्त टाइल की सेवा नहीं कर पाएगा।
SE-0353 ने Swift 5.7 में सीमित existential प्रकार जोड़े, जिनसे existential के प्राथमिक संबद्ध प्रकारों को निर्दिष्ट प्रकारों से बाँधा जा सकता है। एक संबद्ध प्रकार को बाँधना अब भी एक निश्चित संबंध दर्शाता है। इससे मनमाने सामग्री प्रकारों के लिए कॉलबैक कार्यान्वयनों का समूह उपलब्ध नहीं होता। सीमित Existential प्रकार
कंपाइलर समर्थन को डिप्लॉयमेंट समर्थन से अलग रखें। SE-0353 ऐसे रनटाइम बदलावों का स्पष्ट उल्लेख करता है, जिन्हें मौजूदा OS रिलीज़ में पीछे की ओर डिप्लॉय नहीं किया जा सकता। पुराने डिप्लॉयमेंट टार्गेट पर विकल्प अपनाने से पहले, वास्तविक टार्गेट में उपलब्धता से जुड़े निदान जाँचें। ऊपर का पूरा उदाहरण सामान्य डेलीगेट existential इस्तेमाल करता है और इसके लिए सीमित existential स्टोरेज आवश्यक नहीं है। सीमित Existential प्रकार
विफल अनुकूलन की उपयोगी क्रम में जाँच करें
- कॉलबैक का अपेक्षित दायरा जाँचें। अगर एक डेलीगेट को टेक्स्ट और वृत्त, दोनों टाइलें संभालनी हैं, तो उसका witness जेनेरिक विधि हस्ताक्षर बनाए रखे। केवल
Tile<Text>स्वीकार करने वाली विधि उस आवश्यकता को पूरा नहीं करती। - घटक के जेनेरिक पैरामीटर जाँचें। इस डिज़ाइन में,
TileContentपर निर्भर करता है; उसके प्रकार में डेलीगेट का ठोस प्रकार शामिल नहीं है। - एक्टर आइसोलेशन जाँचें। UI कॉलबैक और इस उदाहरण के observable मॉडल को मुख्य एक्टर पर रखें। किसी अन्य आइसोलेशन संदर्भ से कॉल करने पर उस सीमा का पालन होना चाहिए।
- इवेंट की जगह जाँचें। चयन बटन कार्रवाई से भेजें;
bodyकी गणना करते समय या व्यू को इनिशियलाइज़ करते समय नहीं। - मॉडल का स्वामित्व जाँचें। समाहित व्यू में
@StateObjectबनाए रखें और कॉलबैक के लिए मॉडल का दूसरा इंस्टेंस न बनाएँ।
अगर अनुकूलन फिर भी विफल हो, तो उसे एक टाइल और एक डेलीगेट कार्यान्वयन तक सीमित करें, फिर दूसरी सामग्री का प्रकार वापस जोड़ें। इस क्रम से प्रोटोकॉल witness की समस्या और व्यू संयोजन की समस्या में अंतर पता चलता है। उदाहरण के दो अलग-अलग सामग्री प्रकार अनुबंध की उपयोगी जाँच हैं: दोनों बिना cast या सामग्री मिटाए उसी जेनेरिक कॉलबैक तक पहुँचने चाहिए।
मुख्य डिज़ाइन निर्णय यह है कि प्रकारों में भिन्नता कहाँ रखी जाए। हर घटक विशेषीकरण संभालने वाले डेलीगेट के लिए, यह भिन्नता कॉलबैक विधि पर रखें और घटक के प्रकार को डेलीगेट की ठोस पहचान से स्वतंत्र रखें। नतीजे में टाइपयुक्त भेजने वाला बना रहता है, जबकि macOS चयन प्रवाह इतना छोटा रहता है कि उसकी जाँच और विस्तार किया जा सके।
स्रोत
- जेनेरिक — The Swift Programming Language
- प्रोटोकॉल — The Swift Programming Language
- घोषणाएँ — The Swift Programming Language
- संबद्ध प्रकारों पर पुनरावर्ती बाधाओं का समर्थन करें — Swift Evolution
- एक्सिस्टेंशियल any प्रस्तुत करें — Swift Evolution
- प्राथमिक संबद्ध प्रकारों के लिए हल्की समान-प्रकार आवश्यकताएँ — Swift Evolution
- सीमित Existential प्रकार — Swift Evolution
- View — Apple Developer Documentation
- StateObject — Apple Developer Documentation
- Button — Apple Developer Documentation