रिकर्सिव Swift एनम में नेस्टेड प्लेन वैल्यू बनाए रखने के लिए, हर वैल्यू प्रकार के लिए कस्टम एन्कोडिंग और डिकोडिंग लागू करें। यह वॉकथ्रू एक सिंथेटिक Mac ऐप मॉडल बनाता है, जो एनम-केस रैपर जोड़े बिना पूरी तरह मेमोरी में Foundation ऑब्जेक्ट्स के माध्यम से राउंड-ट्रिप करता है।
सिंथेसाइज़ किया गया Codable निरूपण को क्यों बदल देता है?
मान लें कि किसी Mac डेस्कटॉप ऐप में एक छोटा लेआउट प्रीव्यू है, जिसमें लेबल, संख्यात्मक वैल्यू, स्विच, सूचियाँ और नेस्टेड समूह हैं। एक रिकर्सिव एनम इन वैल्यू का सटीक वर्णन कर सकता है। यहाँ ठोस काम इस एनम को एक प्लेन Foundation ऑब्जेक्ट ग्राफ़ में बदलना और समर्थित वैल्यू प्रकारों को बनाए रखते हुए उसे फिर से बनाना है।
स्वचालित conformance अपेक्षित निरूपण नहीं देता। Swift का लागू किया गया एनम-सिंथेसिस डिज़ाइन केस का नाम बाहरी keyed container में रखता है, फिर उससे जुड़े वैल्यू को दूसरे container में रखता है। बिना लेबल वाले associated values को _0 जैसी जनरेट की गई keys मिलती हैं। इसलिए स्ट्रिंग केस प्लेन स्ट्रिंग बनने के बजाय संरचनात्मक रैपर हासिल कर लेता है। ये नियम associated values वाले एनम के लिए Codable synthesis में दर्ज हैं।
CodingKeys बदलकर इन रैपर के नाम बदले जा सकते हैं, लेकिन synthesis बनाए रखने का अर्थ है उसका संरचनात्मक मॉडल भी बनाए रखना। समर्थित समाधान है कि आप खुद init(from:) और encode(to:) लागू करें। Apple उन प्रकारों के लिए मैन्युअल conformance को स्पष्ट रूप से दर्ज करता है जिनकी Swift संरचना और एन्कोड की गई संरचना अलग होती है: कस्टम प्रकारों को एन्कोड और डिकोड करना।
रिकर्शन और निरूपण अलग-अलग निर्णय हैं। Swift का indirect modifier रिकर्सिव एनम स्टोरेज सक्षम करता है; कस्टम कोडिंग विधियाँ एन्कोड किए गए आकार को तय करती हैं। भाषा गाइड बताती है कि associated-value केस पर indirection लागू करने के लिए एनम घोषणा से पहले indirect रखा जाता है: Enumerations।
प्लेन निरूपण में आखिर क्या-क्या बनाए रखना चाहिए?
इस उदाहरण के लिए निम्नलिखित अनुबंध अपनाएँ। यह जानबूझकर एक छोटा वैल्यू-समूह समर्थित करता है, जो सिंथेटिक लेआउट प्रीव्यू के लिए उपयुक्त है। Foundation के दस्तावेज़ों में दिए गए रूपांतरण नियम स्ट्रिंग, नंबर, ऐरे, डिक्शनरी और null ऑब्जेक्ट की अनुमति देते हैं, डिक्शनरी keys को स्ट्रिंग होना आवश्यक बनाते हैं और nonfinite नंबरों को बाहर रखते हैं। देखें JSONSerialization।
| एनम केस | प्लेन वैल्यू | संगतता की आवश्यकता |
|---|---|---|
null |
NSNull |
स्पष्ट null वैल्यू बनाए रखें |
bool |
Boolean वैल्यू | इसे संख्यात्मक शून्य और एक से अलग रखें |
number |
सीमित Double वैल्यू |
Double की परिशुद्धता-सीमाएँ स्वीकार करें |
string |
स्ट्रिंग | संख्यात्मक रूपांतरण के बिना टेक्स्ट बनाए रखें |
array |
प्लेन वैल्यू का ऐरे | एलिमेंट क्रम और खाली ऐरे बनाए रखें |
object |
स्ट्रिंग-key वाली डिक्शनरी | keys, नेस्टेड वैल्यू और खाली ऑब्जेक्ट बनाए रखें |
यहाँ संगतता का अर्थ है कि बताई गई संख्यात्मक सीमाओं के भीतर इन श्रेणियों और उनकी वैल्यू को बनाए रखा जाए। इसमें किसी विशिष्ट Foundation नंबर सबक्लास या डिक्शनरी keys के क्रम को बनाए रखना आवश्यक नहीं है। स्पष्ट null एंट्री भी बनी रहनी चाहिए: "subtitle": NSNull() वाली ऑब्जेक्ट का मॉडल अर्थ उस ऑब्जेक्ट से अलग है जिसमें subtitle key मौजूद नहीं है।
इस अनुबंध को अपनाने से पहले तय करें कि क्या नंबर वास्तव में एक ही Double केस साझा कर सकते हैं। Apple Double को 64-बिट फ़्लोटिंग-पॉइंट प्रकार के रूप में दर्ज करता है और exact-conversion initializers देता है, क्योंकि सामान्य रूपांतरणों में rounding हो सकती है। अगर आपके मॉडल को बड़े पूर्णांकों की सटीकता चाहिए, तो उन्हें बदलने से पहले एक स्पष्ट संख्यात्मक नीति चुनें। एक integer केस जोड़ने के लिए डिकोडिंग नियम भी चाहिए, क्योंकि प्लेन संख्यात्मक वैल्यू में Swift एनम-केस पहचान नहीं होती। देखें Double।
रिकर्सिव एनम कैसे लागू करें?
Foundation उपलब्ध होने वाले मौजूदा Mac Swift वातावरण का उपयोग करें। अगले तीन कोड ब्लॉक क्रम से मिलकर एक पूरा टॉप-लेवल उदाहरण बनाते हैं: एनम, रूपांतरण फ़ंक्शन और चलाया जा सकने वाला जाँच रूटीन। सभी नमूना वैल्यू मेमोरी में बनाई जाती हैं। शोध वातावरण में Swift उपलब्ध नहीं है, इसलिए उदाहरण की जाँच की गई, लेकिन उसे चलाया नहीं गया।
डिकोडर पहले null जाँचता है, फिर समर्थित आदिम प्रकारों को आज़माता है। Apple का SingleValueDecodingContainer decodeNil() सहित ये ऑपरेशन उपलब्ध कराता है। स्थानीय helper केवल type mismatch को दूसरे आदिम प्रकार को आज़माने का कारण मानता है; अन्य डिकोडिंग त्रुटियाँ आगे भेज दी जाती हैं।
import Foundation
indirect enum PlainValue: Codable, Equatable {
case null
case bool(Bool)
case number(Double)
case string(String)
case array([PlainValue])
case object([String: PlainValue])
init(from decoder: Decoder) throws {
let scalar = try decoder.singleValueContainer()
if scalar.decodeNil() {
self = .null
return
}
func matching<T: Decodable>(_ type: T.Type) throws -> T? {
do {
return try scalar.decode(type)
} catch DecodingError.typeMismatch(_, _) {
return nil
}
}
if let value = try matching(Bool.self) {
self = .bool(value)
return
}
if let value = try matching(String.self) {
self = .string(value)
return
}
if let value = try matching(Double.self) {
guard value.isFinite else {
throw DecodingError.dataCorrupted(.init(
codingPath: decoder.codingPath,
debugDescription: "Expected a finite number."
))
}
self = .number(value)
return
}
var sequence: UnkeyedDecodingContainer?
do {
sequence = try decoder.unkeyedContainer()
} catch DecodingError.typeMismatch(_, _) {
sequence = nil
}
if var items = sequence {
var values: [PlainValue] = []
while !items.isAtEnd {
values.append(try items.decode(PlainValue.self))
}
self = .array(values)
return
}
self = .object(try scalar.decode([String: PlainValue].self))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .bool(let value):
try container.encode(value)
case .number(let value):
guard value.isFinite else {
throw EncodingError.invalidValue(value, .init(
codingPath: encoder.codingPath,
debugDescription: "Expected a finite number."
))
}
try container.encode(value)
case .string(let value):
try container.encode(value)
case .array(let values):
try container.encode(values)
case .object(let values):
try container.encode(values)
}
}
}
हर एन्कोडिंग शाखा अपना payload सीधे लिखती है। null शाखा स्पष्ट रूप से encodeNil() को कॉल करती है, और collection शाखाएँ ऐरे तथा डिक्शनरी के coding implementations को सौंप दी जाती हैं। Apple आदिम और generic दोनों encoding operations को SingleValueEncodingContainer में, और collection conformance को कस्टम प्रकारों को एन्कोड और डिकोड करना में दर्ज करता है।
ऐरे की पहचान को ऐरे-एलिमेंट डिकोडिंग से जानबूझकर अलग रखा गया है। अगर मौजूदा वैल्यू ऐरे नहीं है, तो unkeyed container माँगने पर type mismatch आ सकता है। ऐरे पहचान लेने के बाद हर एलिमेंट को सामान्य try से डिकोड किया जाता है, इसलिए किसी एलिमेंट की विफलता आगे जाती है और उसके कारण असंबंधित ऑब्जेक्ट-डिकोडिंग का प्रयास नहीं होता। यह दस्तावेज़ित व्यवहार unkeyedContainer() और UnkeyedDecodingContainer में बताया गया है।
यह implementation Foundation के JSON coding path और बताए गए वैल्यू-समूह के लिए है। किसी अन्य encoder या decoder का उपयोग करने से पहले उसे इसी अनुबंध के विरुद्ध जाँचें; केवल Codable conformance से अलग-अलग निरूपण प्रारूपों में समान व्यवहार सिद्ध नहीं होता।
Foundation ऑब्जेक्ट्स में पूरी तरह मेमोरी के भीतर रूपांतरण कैसे करें?
Foundation दो उपयोगी सीमाएँ देता है: JSONEncoder किसी encodable वैल्यू से Data बनाता है, और JSONDecoder Data से decodable वैल्यू को फिर से बनाता है। JSONSerialization इस मध्यवर्ती निरूपण और Foundation ऑब्जेक्ट्स के बीच रूपांतरण करता है। इन दस्तावेज़ित ऑपरेशन को मिलाने पर यह इन-मेमोरी ब्रिज मिलता है। देखें JSONEncoder, JSONDecoder और JSONSerialization।
func foundationObject(from value: PlainValue) throws -> Any {
let bytes = try JSONEncoder().encode(value)
return try JSONSerialization.jsonObject(
with: bytes,
options: [.fragmentsAllowed]
)
}
func plainValue(from object: Any) throws -> PlainValue {
let bytes = try JSONSerialization.data(
withJSONObject: object,
options: [.fragmentsAllowed]
)
return try JSONDecoder().decode(PlainValue.self, from: bytes)
}
मध्यवर्ती Data मेमोरी में ही रहता है। ये फ़ंक्शन serialization का काम करते हैं; ये एनम और Foundation collections के बीच zero-copy casts नहीं हैं। रूपांतरण को उस सीमा पर रखें जहाँ आपके डेस्कटॉप फ़ीचर को वास्तव में प्लेन ऑब्जेक्ट ग्राफ़ चाहिए, और typed model ऑपरेशन के लिए एनम बनाए रखें।
दोनों .fragmentsAllowed विकल्प ज़रूरी हैं, क्योंकि रिकर्सिव वैल्यू का root scalar हो सकता है। टॉप-लेवल स्ट्रिंग, Boolean, नंबर या null को भी नेस्टेड वैल्यू जैसा ही संभालना होता है। Foundation इस व्यवहार को अलग-अलग ReadingOptions.fragmentsAllowed और WritingOptions.fragmentsAllowed में दर्ज करता है।
रिवर्स फ़ंक्शन Any स्वीकार करता है, क्योंकि Foundation API का input प्रकार यही है, लेकिन इसका समर्थित दायरा ऊपर दिए गए अनुबंध तक ही रहता है। रूपांतरण त्रुटियाँ कॉलर तक पहुँचती हैं। असफल रूपांतरण को खाली ऑब्जेक्ट से न बदलें और रूपांतरण में विफल होने वाले एलिमेंट न हटाएँ; ऐसा करने से मॉडल चुपचाप बदल जाएगा।
कौन-सी जाँचें संगतता दिखाती हैं?
केवल राउंड ट्रिप से मूल समस्या छूट सकती है: सिंथेसाइज़ की गई एनम कोडिंग अपनी रैपर वाली संरचना को सफलतापूर्वक राउंड-ट्रिप कर सकती है। एनम समानता और प्लेन आकार, दोनों जाँचें। अगला रूटीन एक स्वतंत्र Foundation ग्राफ़ बनाता है, उसके अपेक्षित एनम मॉडल की पुष्टि करता है, आउटपुट की वास्तविक root keys जाँचता है और हर root श्रेणी आज़माता है।
func runExample() throws {
let plain: [String: Any] = [
"title": "Tile preview",
"enabled": true,
"spacing": 8.5,
"subtitle": NSNull(),
"tiles": [
["label": "Circle", "size": 24.0],
["label": "Square", "size": 32.0]
]
]
let expected: PlainValue = .object([
"title": .string("Tile preview"),
"enabled": .bool(true),
"spacing": .number(8.5),
"subtitle": .null,
"tiles": .array([
.object([
"label": .string("Circle"),
"size": .number(24)
]),
.object([
"label": .string("Square"),
"size": .number(32)
])
])
])
let model = try plainValue(from: plain)
precondition(model == expected)
let output = try foundationObject(from: model)
guard let object = output as? [String: Any] else {
preconditionFailure("Expected a plain object root.")
}
precondition(Set(object.keys) == Set(plain.keys))
precondition(object["title"] as? String == "Tile preview")
precondition(object["subtitle"] is NSNull)
precondition((object["tiles"] as? [Any])?.count == 2)
let restored = try plainValue(from: output)
precondition(restored == expected)
let roots: [PlainValue] = [
.null, .bool(false), .bool(true),
.number(0), .number(1), .number(8.5),
.string("1"), .array([]), .object([:]),
expected
]
for root in roots {
let graph = try foundationObject(from: root)
let decoded = try plainValue(from: graph)
precondition(decoded == root)
}
do {
_ = try foundationObject(from: .number(.infinity))
preconditionFailure("Expected nonfinite number rejection.")
} catch EncodingError.invalidValue(_, _) {
// Expected: this model requires finite numbers.
}
print("All compatibility checks passed.")
}
try runExample()
अपेक्षित सफलता संदेश है All compatibility checks passed. Root-key जाँच किसी अतिरिक्त बाहरी केस रैपर को पकड़ती है। स्वतंत्र अपेक्षित मॉडल गलत केस चयन पकड़ता है, जिसमें Boolean और नंबर का भ्रम तथा नंबर जैसा दिखने वाला टेक्स्ट और नंबर का भ्रम भी शामिल है। null जाँच पुष्टि करती है कि subtitle मौजूद रहता है, जबकि root जाँचें scalar fragments कवर करती हैं और खाली ऐरे को खाली ऑब्जेक्ट से अलग करती हैं।
किसी मौजूदा डेस्कटॉप मॉडल के लिए, इस जाँच क्रम को उसके सिंथेटिक वैल्यू के अनुसार ढालें: पहले अपेक्षित एनम जाँचें, फिर प्लेन आकार देखें, फिर एनम को दोबारा बनाएँ और अंत में सीमा-स्थितियाँ आज़माएँ। मध्यवर्ती नतीजों को नाम दें, ताकि जाँच विफल होने पर पता चले कि किस रूपांतरण ने वैल्यू बदली।
इस तरीके की सीमाएँ क्या हैं?
मुख्य सीमा संख्यात्मक अनुबंध है। एक Double केस हर संख्यात्मक Foundation input को सटीकता से बनाए रखने का वादा नहीं कर सकता। जहाँ पूर्णांक का सटीक रूपांतरण मायने रखता है, वहाँ इस मॉडल में आने से पहले उसकी representability जाँचें या संख्यात्मक केस और उनके डिकोडिंग नियम फिर से डिज़ाइन करें। Serialization का सफल होना संख्यात्मक सटीकता बनाए रहने का प्रमाण नहीं है। Apple का Double दस्तावेज़ rounding वाले रूपांतरण और exact रूपांतरण के बीच अंतर बताता है।
यह उदाहरण दोनों coding दिशाओं में nonfinite नंबरों को स्पष्ट रूप से अस्वीकार भी करता है। इससे एनम की सीमा प्लेन Foundation निरूपण की दस्तावेज़ित पाबंदियों के अनुरूप रहती है। रिकर्सिव संरचना का अर्थ असीमित nesting क्षमता नहीं है, इसलिए सिंथेटिक मॉडल ट्री उचित आकार के रखें और रूपांतरण त्रुटियों को कॉलिंग फ़ीचर तक पहुँचने दें।
लेआउट-प्रीव्यू कार्य के लिए, इसका अंतिम परिणाम एक typed रिकर्सिव एनम है, जिसकी कस्टम coding प्लेन स्ट्रिंग, Boolean, सीमित नंबर, null, ऐरे और स्ट्रिंग-key वाले ऑब्जेक्ट बनाए रखती है। केस या रूपांतरण लॉजिक बदलते समय राउंड-ट्रिप जाँचों के साथ आकार की जाँचें भी रखें; दोनों मिलकर अपेक्षित निरूपण को ठोस और समीक्षा योग्य बनाती हैं।
स्रोत
- associated values वाले एनम के लिए Codable synthesis — Swift Project.
- Enumerations — Swift Project.
- कस्टम प्रकारों को एन्कोड और डिकोड करना — Apple.
- SingleValueDecodingContainer — Apple.
- SingleValueEncodingContainer — Apple.
- unkeyedContainer() — Apple.
- UnkeyedDecodingContainer — Apple.
- JSONEncoder — Apple.
- JSONDecoder — Apple.
- JSONSerialization — Apple.
- ReadingOptions.fragmentsAllowed — Apple.
- WritingOptions.fragmentsAllowed — Apple.
- Double — Apple.