असाइनमेंट के बाद Swift struct परिवर्तनीय ऑब्जेक्ट साझा कर सकता है। कॉपियों को स्वतंत्र रखने के लिए वैल्यू-टाइप प्रॉपर्टीज़ इस्तेमाल करें या क्लास स्टोरेज को कॉपी-ऑन-राइट के पीछे छिपाएँ। यह गाइड Mac डेस्कटॉप प्रीव्यू के लिए एक सिंथेटिक टाइल-पंक्ति कैलकुलेटर बनाती है और जाँचती है कि एक कॉपी को संपादित करने से बाकी कॉपियाँ अपरिवर्तित रहती हैं।
struct की कॉपी करने पर भी मूल में बदलाव क्यों होता है?
मान लें कि डेस्कटॉप प्रीव्यू टाइलों की एक पंक्ति की चौड़ाई निकालता है। आप किसी दूसरी टाइल चौड़ाई को आज़माने के लिए उसकी सेटिंग्स की कॉपी बनाते हैं और उम्मीद करते हैं कि मूल प्रीव्यू अपरिवर्तित रहेगा। यह उम्मीद तब पूरी नहीं होती जब दोनों सेटिंग्स वैल्यू में एक ही परिवर्तनीय ऑब्जेक्ट का संदर्भ हो।
Swift किसी struct को दूसरी वैरिएबल में असाइन करते समय उसके स्टोर किए गए वैल्यू कॉपी करता है। क्लास प्रॉपर्टी के लिए यह स्टोर किया गया वैल्यू एक संदर्भ होता है: संदर्भित ऑब्जेक्ट साझा बना रहता है। रैपर अब भी struct है, लेकिन उसके दिखाई देने वाले व्यवहार में साझा म्यूटेशन शामिल हो सकता है। ये नियम Swift के वैल्यू और रेफ़रेंस टाइप्स के दस्तावेज़ित अंतर के अनुसार हैं।
यह पूरा उदाहरण समस्या दिखाता है। इसमें केवल Swift की स्टैंडर्ड लाइब्रेरी का उपयोग है, इसलिए किसी इंपोर्ट की ज़रूरत नहीं है।
final class TileDimensions {
var width: Int
init(width: Int) {
self.width = width
}
}
struct SharedLayout {
let dimensions: TileDimensions
}
let original = SharedLayout(
dimensions: TileDimensions(width: 40)
)
let edited = original
edited.dimensions.width = 60
assert(original.dimensions === edited.dimensions)
assert(original.dimensions.width == 60)
print(original.dimensions.width) // 60
प्रॉपर्टी या रैपर को let के रूप में घोषित करने से ऑब्जेक्ट की परिवर्तनीय प्रॉपर्टीज़ फ़्रीज़ नहीं होतीं। इससे उस संदर्भ या struct वैल्यू को बदलना रुकता है। Swift का Structures and Classes अध्याय कॉन्स्टेंट संदर्भों और अपरिवर्तनीय ऑब्जेक्ट स्थिति के बीच स्पष्ट अंतर करता है।
इन ऑब्जेक्ट को ऐरे में रखने से उनकी साझा पहचान बनी रहती है। ऐरे की कॉपियाँ अपने एलिमेंट्स को स्वतंत्र रूप से प्रबंधित करती हैं, लेकिन क्लास-वैल्यू वाला एलिमेंट स्वयं एक संदर्भ होता है। किसी एलिमेंट को बदलने से एक ऐरे प्रभावित होता है; दोनों ऐरे द्वारा संदर्भित ऑब्जेक्ट को संशोधित करना दोनों में दिखाई देता है। Apple इस अंतर को Array के दस्तावेज़ में दिखाता है।
कौन-सा डिज़ाइन कैलकुलेशन को स्वतंत्र वैल्यू देता है?
इंप्लीमेंटेशन चुनने से पहले अपेक्षित व्यवहार तय करें: var edited = original के बाद, किसी भी लेआउट में समर्थित संपादन से दूसरे लेआउट की गणना की गई चौड़ाई अपरिवर्तित रहनी चाहिए। टाइप के उपयोगकर्ताओं को यह जानने की ज़रूरत नहीं होनी चाहिए कि उसका स्टोरेज अंदरूनी तौर पर साझा है या नहीं।
नीचे दिया गया निर्णय-सहायक इस अपेक्षित व्यवहार को स्थानीय प्रीव्यू मॉडल पर लागू करता है। यह दस्तावेज़ित कॉपी नियमों पर आधारित डिज़ाइन सुझाव है।
| स्थिति | उपयुक्त डिज़ाइन | स्वतंत्रता बनाए रखने की शर्त |
|---|---|---|
| सेटिंग्स में संख्याएँ, स्ट्रिंग या अन्य वैल्यू-सेमांटिक्स वाले घटक हैं | उन वैल्यू को सीधे struct में स्टोर करें | जाँचें कि नेस्टेड घटक भी स्वतंत्र दिखाई देने वाले वैल्यू प्रदान करते हैं |
| मौजूदा स्टोरेज को क्लास-आधारित ही रखना है | निजी कॉपी-ऑन-राइट स्टोरेज इस्तेमाल करें | हर म्यूटेशन से पहले साझा परिवर्तनीय स्थिति को अलग करना होगा |
| अलग स्नैपशॉट की ज़रूरत केवल किसी नामित ऑपरेशन पर है | स्पष्ट कॉपी ऑपरेशन दें | बताएं कि जब तक रैपर स्वतंत्रता लागू न करे, सामान्य असाइनमेंट ऑब्जेक्ट साझा करता है |
| प्रोग्राम के कई हिस्से जानबूझकर एक ही ऑब्जेक्ट संपादित करते हैं | रेफ़रेंस सेमांटिक्स बनाए रखें | API में साझा ऑब्जेक्ट का व्यवहार बताएँ |
तीन पूर्णांक सेटिंग्स के लिए, सीधे struct प्रॉपर्टीज़ सबसे सरल विकल्प हैं। नीचे दिया क्लास-आधारित इंप्लीमेंटेशन तब उपयोगी है जब किसी मौजूदा स्टोरेज डिज़ाइन को अनुकूलित करना हो या यह समझना हो कि सीमा कैसे काम करती है; यह इस छोटे कैलकुलेशन के लिए प्रदर्शन में बढ़त साबित नहीं करता।
कॉपी-ऑन-राइट दो वैल्यू को तब तक स्टोरेज साझा करने देता है जब तक उनमें से कोई एक बदलता नहीं। Apple Explore Swift performance में क्लास को struct में रैप करने और कॉपी-ऑन-राइट लागू करने को अलग-अलग आवंटित स्टोरेज के साथ वैल्यू सेमांटिक्स जोड़ने का तरीका बताता है। केवल क्लास को रैप करने से यह व्यवहार लागू नहीं होता।
केवल स्पष्ट copy() मेथड देने से भी एक कमी रह जाती है: कॉलर उसे चलाए बिना सामान्य असाइनमेंट कर सकते हैं। अगर असाइनमेंट आपके वैल्यू कॉन्ट्रैक्ट का हिस्सा है, तो रैपर के म्यूटेशन पाथ को स्वतंत्रता अपने-आप लागू करनी होगी।
कॉपी-ऑन-राइट कैसे लागू करें और जाँचें?
एक निजी स्टोरेज ऑब्जेक्ट इस्तेमाल करें, उसे रैपर के भीतर बनाएँ और केवल गणना किए गए वैल्यू तथा नियंत्रित म्यूटेशन मेथड उजागर करें। स्टोरेज बदलने से ठीक पहले isKnownUniquelyReferenced कॉल करें। अगर विशिष्टता की पुष्टि नहीं होती, तो स्टोरेज को स्वतंत्र कॉपी से बदलें। Apple कॉपी-ऑन-राइट लागू करने के लिए यही क्रम दस्तावेज़ित करता है।
इस उदाहरण में समझाने के लिए तीन टाइलें ली गई हैं, जिनमें हर टाइल 40 पॉइंट चौड़ी है और आस-पास की टाइलों के बीच 10 पॉइंट का गैप है। दो गैप हैं, इसलिए पंक्ति की चौड़ाई 3 * 40 + 2 * 10 = 140 पॉइंट होती है। टाइल की चौड़ाई 60 करने पर कुल चौड़ाई 200 पॉइंट होती है।
Mac पर पूरा प्रोग्राम आज़माने के लिए, पहले Terminal में swift --version चलाकर पुष्टि करें कि Swift टूलचेन उपलब्ध है, जैसा Swift की Getting Started गाइड सुझाती है। नीचे दिया कोड Layout.swift नाम से सेव करें, फिर उसकी डायरेक्टरी से swift Layout.swift चलाएँ। प्रोग्राम मेमोरी में गणना करके नतीजे प्रिंट करता है।
struct TileLayout {
private final class Storage {
let columns: Int
var tileWidth: Int
var gap: Int
init(columns: Int, tileWidth: Int, gap: Int) {
self.columns = columns
self.tileWidth = tileWidth
self.gap = gap
}
func copy() -> Storage {
Storage(
columns: columns,
tileWidth: tileWidth,
gap: gap
)
}
}
private var storage: Storage
init(columns: Int, tileWidth: Int, gap: Int) {
precondition((1...1000).contains(columns))
precondition((0...1000).contains(tileWidth))
precondition((0...1000).contains(gap))
storage = Storage(
columns: columns,
tileWidth: tileWidth,
gap: gap
)
}
var rowWidth: Int {
storage.columns * storage.tileWidth
+ (storage.columns - 1) * storage.gap
}
private mutating func ensureUniqueStorage() {
if !isKnownUniquelyReferenced(&storage) {
storage = storage.copy()
}
}
mutating func setTileWidth(_ width: Int) {
precondition((0...1000).contains(width))
ensureUniqueStorage()
storage.tileWidth = width
}
mutating func setGap(_ gap: Int) {
precondition((0...1000).contains(gap))
ensureUniqueStorage()
storage.gap = gap
}
}
var original = TileLayout(columns: 3, tileWidth: 40, gap: 10)
var edited = original
edited.setTileWidth(60)
assert(original.rowWidth == 140)
assert(edited.rowWidth == 200)
original.setGap(5)
assert(original.rowWidth == 130)
assert(edited.rowWidth == 200)
let snapshot = edited
edited.setGap(20)
assert(snapshot.rowWidth == 200)
assert(edited.rowWidth == 220)
print(original.rowWidth, snapshot.rowWidth, edited.rowWidth)
// Expected output: 130 200 220
प्रोग्राम को किसी इंपोर्ट या बाहरी पैकेज की ज़रूरत नहीं है। स्वीकार्य रेंज जानबूझकर उदाहरण के लिए तय की गई हैं: एक से 1,000 कॉलम और चौड़ाई तथा गैप के लिए शून्य से 1,000 पॉइंट। इससे पूर्णांक गणना सीमित रहती है। रेंज से बाहर का आर्ग्युमेंट प्रीकंडीशन विफल करता है; उसे चुपचाप सीमा के भीतर नहीं किया जाता। अगर ये सेटर बाद में डेस्कटॉप इंटरफ़ेस से संपादित किए जा सकने वाले टेक्स्ट स्वीकार करें, तो इन्हें कॉल करने से पहले उस इनपुट को वैलिडेट करें और उपयोगकर्ता को सुधार का रास्ता दें।
जाँच तीन दिखाई देने वाली ज़रूरतें पूरी करती हैं: कॉपी किए गए लेआउट को संपादित करने पर मूल सुरक्षित रहता है, मूल को संपादित करने पर कॉपी सुरक्षित रहती है और कॉन्स्टेंट स्नैपशॉट को बनाए रखने पर बाद के संपादन के दौरान उसका नतीजा सुरक्षित रहता है। ये किसी खास आवंटन संख्या की माँग किए बिना व्यवहार जाँचती हैं। शोध परिवेश में Swift उपलब्ध नहीं था, इसलिए वहाँ इस उदाहरण का निरीक्षण किया गया, इसे चलाया नहीं गया; दिखाया गया आउटपुट अपेक्षित नतीजा है।
रैपर बढ़ने पर क्या सही रहना चाहिए?
इस उदाहरण की स्वतंत्रता पूरी API सीमा से आती है। स्टोरेज निजी है, कोई इनिशियलाइज़र बाहर से रखे गए स्टोरेज ऑब्जेक्ट को स्वीकार नहीं करता और कोई गेटर उसे लौटाता नहीं है। दोनों म्यूटेशन मेथड एक ही डिटैचमेंट ऑपरेशन से होकर गुजरते हैं। गेटर स्टोरेज बदले बिना नतीजे की गणना करता है।
फ़ीचर जोड़ते समय, स्थिति को बदल सकने वाले हर नए पाथ का पता लगाएँ। नए सेटर, सबस्क्रिप्ट या एडिटिंग कॉलबैक को भी मौजूदा मेथड जैसा ही व्यवहार चाहिए। अगर गेटर परिवर्तनीय चाइल्ड ऑब्जेक्ट लौटाता है, तो कॉलर डिटैचमेंट को दरकिनार कर सकते हैं, भले ही गेटर स्वयं केवल पढ़ने के लिए हो।
स्टोरेज कॉपी में वैल्यू से जुड़ी सारी परिवर्तनीय स्थिति भी शामिल होनी चाहिए। यहाँ Storage.copy() तीन पूर्णांकों की कॉपी करता है, जो पर्याप्त है। अगर स्टोरेज में बाद में परिवर्तनीय क्लास-वैल्यू वाला चाइल्ड जुड़ता है, तो उस चाइल्ड को नए स्टोरेज में असाइन करने से एक और साझा संदर्भ बना रहेगा। उस चाइल्ड को उचित तरीके से कॉपी करें या ऐसे घटक से बदलें जो पहले से वैल्यू सेमांटिक्स देता हो। Array के लिए दस्तावेज़ित क्लास-एलिमेंट का व्यवहार इसी सीमा को दिखाता है।
कैलकुलेटर बढ़ाते समय यह क्रम अपनाएँ:
- हर नए एडिटिंग ऑपरेशन को आज़माने से पहले लेआउट की कॉपी बनाएँ।
- पुष्टि करें कि अपरिवर्तित लेआउट का पिछला नतीजा बना रहता है।
- भूमिकाएँ उलटकर यही दोहराएँ।
- एक स्नैपशॉट को बनाए रखें, बाद में कई संपादन करें और फिर उसे जाँचें।
- लौटाई गई हर प्रॉपर्टी और इनिशियलाइज़र आर्ग्युमेंट को देखें कि कहीं रैपर से परिवर्तनीय संदर्भ बाहर तो नहीं जा सकते।
final स्टोरेज क्लास सबक्लास को नया व्यवहार जोड़ने से रोकता है, लेकिन उसकी प्रॉपर्टीज़ को अपरिवर्तनीय नहीं बनाता। इसी तरह, किसी ऑपरेशन को mutating के रूप में चिह्नित करने से उसे struct अपडेट करने की अनुमति मिलती है; इस उदाहरण की स्वतंत्रता डिटैचमेंट लॉजिक बनाए रखता है।
Foundation ऑब्जेक्ट की कॉपी कैसे करें?
अगर किसी मौजूदा मॉडल में NSMutableString जैसे Foundation ऑब्जेक्ट स्टोर हैं, तो स्टोरेज डिटैच करते समय उस ठोस टाइप के दस्तावेज़ित कॉपी व्यवहार का उपयोग करें। Swift असाइनमेंट अपने-आप Foundation कॉपी मेथड नहीं चलाता।
Foundation का NSCopying प्रोटोकॉल फ़ंक्शनल कॉपी परिभाषित करता है, जबकि उसका सटीक अर्थ क्लास पर निर्भर करता है। इसके दस्तावेज़ यह भी अनुमति देते हैं कि क्लास और उसकी सामग्री, दोनों अपरिवर्तनीय हों, तो मूल ऑब्जेक्ट लौटाया जा सकता है। इसलिए, केवल ऑब्जेक्ट की पहचान से यह तय नहीं किया जा सकता कि कॉपी आपके वैल्यू कॉन्ट्रैक्ट को पूरा करती है या नहीं।
जब ऐसे टाइप से परिवर्तनीय नतीजा चाहिए जिसके परिवर्तनीय और अपरिवर्तनीय रूप अलग हों, तो NSMutableCopying देखें। Apple इस उद्देश्य के लिए mutableCopy(with:) और सुविधाजनक मेथड mutableCopy() दस्तावेज़ित करता है। जिन क्लास में यह अंतर नहीं है, उन्हें इसके बजाय NSCopying अपनाना चाहिए।
टाइल कैलकुलेटर के लिए व्यावहारिक सीमा सीधी है: इनपुट के रूप में स्वीकार किया गया Foundation ऑब्जेक्ट आपकी कथित तौर पर स्वतंत्र सेटिंग्स का बाहरी रूप से परिवर्तनीय एलियास नहीं बना रहना चाहिए। प्रवेश पर उपयुक्त स्वतंत्र स्नैपशॉट लें, संपादन से पहले उसे उचित ढंग से अलग करें और आंतरिक परिवर्तनीय ऑब्जेक्ट के बजाय वैल्यू स्नैपशॉट लौटाएँ। कॉपी ऑपरेशन पर भरोसा करने से पहले जाँचें कि ठोस क्लास किसी भी नेस्टेड परिवर्तनीय सामग्री को कैसे संभालती है।
क्या कॉपी-ऑन-राइट टाइप को Sendable बनाता है?
वैल्यू की स्वतंत्रता और कॉन्करेंसी कन्फॉर्मेंस के लिए अलग-अलग तर्क चाहिए। Apple के Sendable दस्तावेज़ के अनुसार, जाँचे गए sendable struct के स्टोर किए गए मेंबर sendable होने चाहिए। निजी परिवर्तनीय स्टोरेज क्लास केवल इसलिए यह शर्त पूरी नहीं करती कि उसे घेरे हुए टाइप एक struct है।
विशिष्टता जाँच सिंक्रोनाइज़ेशन भी नहीं है। Apple चेतावनी देता है कि एक साथ एक्सेस के दौरान isKnownUniquelyReferenced true लौटा सकता है और ऐसे उपयोग के आसपास उचित सिंक्रोनाइज़ेशन ज़रूरी है। यह मज़बूत संदर्भ जाँचता है; कमज़ोर या अनअवनड संदर्भ फिर भी मौजूद हो सकते हैं। इसलिए, विशिष्टता जाँचने जितना ही ज़रूरी है कि स्टोरेज रैपर के बाहर पहुँच में न हो। स्टैंडर्ड लाइब्रेरी फ़ंक्शन की दस्तावेज़ित सीमाएँ देखें।
इस उदाहरण को एक सिंक्रोनस गणना कॉन्टेक्स्ट तक सीमित रखें। अगर बाद के संस्करण को सेटिंग्स को कॉन्करेंसी डोमेन के बीच भेजना हो, तो sendable वैल्यू प्रॉपर्टीज़ वाला struct और जाँची गई Sendable कन्फॉर्मेंस सरल विकल्प है। @unchecked Sendable जोड़ने से सही होने की ज़िम्मेदारी इंप्लीमेंट करने वाले पर आ जाती है; इससे ज़रूरी कोऑर्डिनेशन अपने-आप नहीं मिलता। Apple Sendable रेफ़रेंस में ये ज़रूरतें समझाता है।
छोटे डेस्कटॉप कैलकुलेशन के लिए, जब सीधे वैल्यू प्रॉपर्टीज़ काम करें तो उन्हें प्राथमिकता दें। जब परिवर्तनीय क्लास स्टोरेज बनाए रखना ज़रूरी हो, तो निजी स्वामित्व, स्टोरेज की पूरी कॉपियों, नियंत्रित संपादन और दिखाई देने वाली स्वतंत्रता की जाँचों के ज़रिए अपेक्षित व्यवहार सुरक्षित रखें। नतीजा अनुमान के मुताबिक होता है: प्रीव्यू की कामकाजी सेटिंग्स बदलने से हर सुरक्षित रखे गए लेआउट वैल्यू का अपना गणना किया हुआ नतीजा बना रहता है।
स्रोत
- Value And Reference Types In Swift — Swift.org.
- Structures and Classes — The Swift Programming Language, Swift project.
- Array — Apple Developer Documentation.
- Explore Swift performance — Apple Developer.
- isKnownUniquelyReferenced(_:) — Apple Developer Documentation.
- Getting Started — Swift.org.
- NSCopying — Apple Developer Documentation.
- NSMutableCopying — Apple Developer Documentation.
- Sendable — Apple Developer Documentation.