Swift स्ट्रिंग में जोड़े गए पाठ को ट्रैक करने के लिए, पुराने स्ट्रिंग इंडेक्स सहेजने के बजाय किसी नामित एन्कोडिंग में ऑफ़सेट रखें। यह गाइड Mac पर एक छोटी स्थानीय टेक्स्ट-पूर्वावलोकन गणना के लिए केवल जोड़ने वाली UTF-16 खंड-लेजर बनाती है।
काम स्पष्ट है: कृत्रिम टेक्स्ट टुकड़ों को जोड़ना, याद रखना कि हर टुकड़ा कहाँ आता है, और बाद में जोड़ने के बाद उसकी सटीक सामग्री फिर से प्राप्त करना। उदाहरण पूरी तरह मेमोरी में चलता है और अपने परिणाम प्रिंट करता है। इसके खंड-सीमाएँ पाठ में योगदानों को दर्शाती हैं, जो प्रदर्शित वर्णों की सीमाओं से अलग हो सकती हैं।
पुराने अंतिम इंडेक्स को सहेजना भरोसेमंद क्यों नहीं है
एक लुभावना तरीका है text.endIndex सहेजना, कोई टुकड़ा जोड़ना और उस सहेजे गए इंडेक्स को नए text.endIndex के साथ जोड़ना। इससे म्यूटेशन से पहले मिला इंडेक्स, म्यूटेशन के बाद की स्ट्रिंग के साथ मिल जाता है। Swift का कलेक्शन दस्तावेज़ स्पष्ट रूप से चेतावनी देता है कि म्यूटेट करने वाली कार्रवाइयाँ सहेजे गए इंडेक्स अमान्य कर सकती हैं। छोटे नमूने के साथ सफल परिणाम से बाद के म्यूटेशन के लिए कोई सामान्य गारंटी स्थापित नहीं होती। Apple का Collection दस्तावेज़ देखें।
इंडेक्स दोबारा बनाने पर भी दूसरी समस्या आती है: पाठ जोड़ने से जोड़ के स्थान पर वर्ण-सीमा बदल सकती है। Swift की भाषा मार्गदर्शिका इसका उदाहरण "cafe" के बाद "\u{0301}" जोड़कर देती है, जो एक संयोजक तीव्र स्वर-चिह्न है। परिणामी पाठ में फिर भी Swift के चार Character मान होते हैं, क्योंकि स्वर-चिह्न पिछले अक्षर से जुड़ जाता है। Strings and Characters देखें।
इसलिए, पुराने वर्णों की संख्या दर्ज करना और तैयार स्ट्रिंग में उतने वर्ण आगे बढ़ना, जोड़े गए टुकड़े की शुरुआत को भरोसेमंद ढंग से वापस नहीं देता। स्वर-चिह्न वाले उदाहरण में जोड़ा गया योगदान अंतिम ग्रैफीम क्लस्टर के भीतर शुरू होता है। उस योगदान के अनुरूप कोई अलग प्रदर्शित वर्ण नहीं है।
जोड़ने की सीमा और वर्ण-सीमा को अलग-अलग अवधारणाएँ मानें। पहली यह दर्ज करती है कि पाठ कैसे जोड़ा गया; दूसरी बताती है कि Swift वर्ण-स्तर की कार्रवाइयों के लिए जोड़े गए पाठ को कैसे समूहित करता है।
लेजर में कौन-से निर्देशांक रखने चाहिए?
इस Mac गणना के लिए पूर्णांक UTF-16 ऑफ़सेट और लंबाइयाँ इस्तेमाल करें। Apple के अनुसार NSString अपने स्ट्रिंग स्थानों और रेंज को UTF-16 कोड यूनिट में व्यक्त करता है। Swift का String.utf16 व्यू मेल खाती कोड यूनिट उपलब्ध कराता है, इसलिए लेजर मापन और Foundation सबस्ट्रिंग निष्कर्षण, दोनों के लिए एक ही निर्देशांक प्रणाली इस्तेमाल कर सकती है। NSString और String का UTF-16 व्यू देखें।
| पाठक का काम | उपयुक्त निरूपण | शर्त |
|---|---|---|
| हर मूल टुकड़े को अलग-अलग वापस पाना | स्वामित्व वाले String मानों की एक ऐरे |
संयुक्त-पाठ के निर्देशांक आवश्यक नहीं हैं |
| संयुक्त पाठ में सटीक योगदानों का स्थान ढूँढ़ना | UTF-16 ऑफ़सेट और लंबाइयाँ | पहले का पाठ अपरिवर्तित रहे |
| प्रदर्शित वर्णों के साथ काम करना | मौजूदा स्ट्रिंग में नए सिरे से बनाए गए वर्ण इंडेक्स | खंड-सीमाओं पर ग्रैफीम सीमाओं के रूप में फिर से विचार करना होगा |
| मनमाने संपादनों के दौरान स्थान सुरक्षित रखना | संपादन-जागरूक स्थान मॉडल | यह केवल जोड़ने वाली लेजर पर्याप्त नहीं है |
नीचे दी गई लेजर के चार स्पष्ट नियम हैं:
- यह खाली स्ट्रिंग से शुरू होती है।
- इसमें पाठ का एकमात्र म्यूटेशन पूरी Swift
Stringजोड़ना है। - हर जोड़ में पिछला UTF-16 कुल और टुकड़े की UTF-16 लंबाई दर्ज होती है।
- खंड संख्या केवल उसी लेजर इंस्टेंस या स्नैपशॉट के संदर्भ में अर्थपूर्ण है जिसने उसे बनाया है।
इन नियमों से ऑफ़सेट उपयोगी बनते हैं। प्रत्यय जोड़ने से पहले की एन्कोड की गई सामग्री अपनी जगह पर रहती है, इसलिए पुराने स्पैन अपने स्थान बनाए रखते हैं। पहले का पाठ डालने, हटाने, बदलने या रूपांतरित करने के लिए अलग अनुबंध चाहिए। यह डिज़ाइन दर्ज स्ट्रिंग निरूपणों से निकला है; Swift की ओर से दी गई स्वचालित स्थान-ट्रैकिंग सुविधा नहीं है।
पूरी स्थानीय खंड-लेजर बनाएँ
Foundation उपलब्ध होने वाला macOS Swift कमांड-लाइन टार्गेट इस्तेमाल करें। उसके नमूना टॉप-लेवल प्रोग्राम को नीचे दिए पूरे प्रोग्राम से बदलें। यह कार्यान्वयन append(contentsOf:) इस्तेमाल करता है, जिसके दस्तावेज़ित Swift सिग्नेचर में एक और String स्वीकार होती है। Apple का append(contentsOf:) संदर्भ देखें।
import Foundation
struct Segment: Sendable {
let utf16Location: Int
let utf16Length: Int
var foundationRange: NSRange {
NSRange(location: utf16Location, length: utf16Length)
}
}
enum LedgerError: Error {
case unknownSegment(Int)
}
struct SegmentLedger: Sendable {
private(set) var text = ""
private(set) var segments: [Segment] = []
private var utf16Total = 0
@discardableResult
mutating func append(_ fragment: String) -> Int {
let length = fragment.utf16.count
let (newTotal, overflow) =
utf16Total.addingReportingOverflow(length)
precondition(!overflow, "UTF-16 total exceeds Int capacity")
let segment = Segment(
utf16Location: utf16Total,
utf16Length: length
)
text.append(contentsOf: fragment)
segments.append(segment)
utf16Total = newTotal
return segments.count - 1
}
func fragment(at segmentID: Int) throws -> String {
guard segments.indices.contains(segmentID) else {
throw LedgerError.unknownSegment(segmentID)
}
let segment = segments[segmentID]
if segment.utf16Length == 0 {
return ""
}
return (text as NSString).substring(
with: segment.foundationRange
)
}
}
let inputs = ["cafe", "\u{0301}", "", "\u{1F7E6}", " done"]
var ledger = SegmentLedger()
for input in inputs {
ledger.append(input)
}
let expectedLocations = [0, 4, 5, 5, 7]
let expectedLengths = [4, 1, 0, 2, 5]
for id in inputs.indices {
let recovered = try ledger.fragment(at: id)
let segment = ledger.segments[id]
// Compare code units, so the check is encoding-exact.
precondition(Array(recovered.utf16) == Array(inputs[id].utf16))
precondition(segment.utf16Location == expectedLocations[id])
precondition(segment.utf16Length == expectedLengths[id])
print("\(id): location=\(segment.utf16Location), " +
"length=\(segment.utf16Length)")
}
precondition(ledger.text.utf16.count == 12)
precondition(ledger.text.count == 10)
// Later appends must preserve earlier segment contents.
ledger.append("!")
for id in inputs.indices {
let recovered = try ledger.fragment(at: id)
precondition(Array(recovered.utf16) == Array(inputs[id].utf16))
}
// Invalid segment numbers produce an explicit error.
do {
_ = try ledger.fragment(at: -1)
preconditionFailure("Expected an unknown-segment error")
} catch LedgerError.unknownSegment(let id) {
precondition(id == -1)
}
print("All segment checks passed.")
प्राप्त API दस्तावेज़ों के आधार पर इस प्रोग्राम की जाँच की गई; यहाँ इसे चलाया नहीं गया, क्योंकि उपलब्ध शेल में Swift एक्ज़ीक्यूटेबल नहीं है। जब आप इसे अपने Mac पर चलाएँगे, तो शामिल प्री-कंडीशन नमूने के अपेक्षित व्यवहार को स्पष्ट करेंगी।
नमूना किन बातों की जाँच के लिए बनाया गया है
अपेक्षित खंड-निर्देशांक लिटरल इनपुट से निकाले गए हैं। संयोजक स्वर-चिह्न एक UTF-16 कोड यूनिट देता है, खाली टुकड़ा शून्य देता है और नीला वर्ग दो देता है। अंतिम प्रत्यय पाँच देता है। ये उदाहरण के लिए इनपुट गणनाएँ हैं, प्रदर्शन मापन नहीं।
| खंड | इनपुट | UTF-16 स्थान | UTF-16 लंबाई |
|---|---|---|---|
| 0 | "cafe" |
0 | 4 |
| 1 | "\u{0301}" |
4 | 1 |
| 2 | "" |
5 | 0 |
| 3 | "\u{1F7E6}" |
5 | 2 |
| 4 | " done" |
7 | 5 |
खाली टुकड़े को अपनी खंड संख्या मिलती है, भले ही उसका स्थान अगले खंड के साथ साझा हो। इससे बिना कोई पाठ गढ़े जोड़ने का इतिहास सुरक्षित रहता है। "" स्पष्ट रूप से लौटाने से इस स्थिति में Foundation सबस्ट्रिंग कॉल भी नहीं करनी पड़ती।
पुनर्प्राप्ति जाँच जानबूझकर UTF-16 ऐरे की तुलना करती है। Swift की स्ट्रिंग समानता कैनोनिकल समतुल्यता पहचानती है, यानी अलग-अलग अंतर्निहित स्केलर क्रम वाली कुछ स्ट्रिंग भी बराबर मानी जाती हैं। इस लेजर के अनुबंध के लिए एन्कोडिंग-स्तर की सटीक जाँच अधिक उपयुक्त है। Strings and Characters में स्ट्रिंग तुलना की चर्चा देखें।
निष्कर्षण में Foundation का इस्तेमाल क्यों होता है
सहेजे गए पूर्णांकों को NSRange में बदला जाता है और सीधे NSString.substring(with:) को दिया जाता है। Apple के अनुसार यह मेथड निर्दिष्ट रेंज निकालता है और यह आवश्यक है कि रेंज रिसीवर की सीमा के भीतर रहे। अमान्य रेंज अपवाद उत्पन्न कर सकती हैं। लेजर पाठ और उसके स्पैन, दोनों नियंत्रित करती है और निष्कर्षण से पहले अज्ञात खंड संख्याओं को अस्वीकार करती है। substring(with:) देखें।
हर दर्ज की गई सीमा पूरी Swift स्ट्रिंग जोड़ने से आती है। उदाहरण कभी भी नीले वर्ग के UTF-16 सरोगेट पेयर के बीच का ऑफ़सेट नहीं बनाता। फिर भी कोई सीमा संयोजित वर्ण क्रम के भीतर आ सकती है, जैसा स्वर-चिह्न दिखाता है; सटीक योगदान वापस पाना ही इस निरूपण का उद्देश्य है।
इन Foundation रेंजों को बनाने के लिए String.count का इस्तेमाल न करें। Apple NSString.length को UTF-16 कोड यूनिट की संख्या के रूप में परिभाषित करता है। यह वर्णों की गिनती से अलग माप है और रेंडर की गई चौड़ाई नहीं बताता। NSString.length देखें।
म्यूटेशन, खंड निर्माण और स्नैपशॉट को साथ रखें
छोटी सिंक्रोनस गणना के लिए वैल्यू टाइप पर्याप्त है: एक मालिक टुकड़े जोड़ता है और परिणाम पढ़ता है। इसके निजी सेटर्स कॉलर को संयुक्त पाठ को खंड तालिका से स्वतंत्र रूप से बदलने से रोकते हैं। कैश किया गया UTF-16 कुल हर जोड़ पर बढ़ती संयुक्त स्ट्रिंग को दोबारा गिनने से भी बचाता है।
अगर कई एसिंक्रोनस टास्क को एक ही लेजर में योगदान देना हो, तो उस लेजर को एकल ऐक्टर के पीछे रखें, या Mac इंटरफ़ेस से संबंधित होने पर उसे @MainActor पर रखें। जोड़ और मेटाडेटा अपडेट एक ही सिंक्रोनस आइसोलेटेड मेथड में करें। Swift दस्तावेज़ करता है कि ऐक्टर-आइसोलेटेड सिंक्रोनस कार्य उस मेथड के भीतर सस्पेंशन पॉइंट पर बाधित नहीं हो सकता। Concurrency देखें।
पाठ और खंड मेटाडेटा को एक वैल्यू स्नैपशॉट के रूप में साथ लौटाएँ। पाठ पढ़ने और खंड तालिका पढ़ने के लिए अलग-अलग await न करें: उन कॉलों के बीच कोई दूसरा जोड़ हो सकता है। उदाहरण की Sendable घोषणाएँ बताती हैं कि वैल्यू आइसोलेशन सीमाओं के पार भेजने के लिए उपयुक्त हैं; वे साझा वैरिएबल पर समकालीन लेखन को क्रमबद्ध नहीं करतीं। Swift का Sendable दस्तावेज़ स्थानांतरित की जा सकने वाली वैल्यू और सुरक्षित रखी गई म्यूटेबल स्थिति के बीच का अंतर समझाता है।
अगर योगदानों का क्रम मायने रखता है, तो जोड़ने से पहले वह क्रम तय करें। ऐक्टर लेजर की आंतरिक संगति की रक्षा करता है, जबकि कौन-सा टुकड़ा पहले आएगा यह तय करना फिर भी एप्लिकेशन का काम है।
जानें कि ये ऑफ़सेट कब काम का जवाब देना बंद कर देते हैं
किसी स्पैन को दोबारा इस्तेमाल करने से पहले यह निदान क्रम अपनाएँ:
- स्वामित्व जाँचें। क्या खंड संख्या इसी लेजर या इसके किसी संगत, केवल जोड़ने वाले वंशज की है?
- म्यूटेशन इतिहास जाँचें। क्या दर्ज स्पैन से पहले का कोई हिस्सा बदला है? अगर हाँ, तो लेजर फिर से बनाएँ या संपादन-जागरूक मॉडल अपनाएँ।
- इकाइयाँ जाँचें। क्या उपभोक्ता के निर्देशांक UTF-16 कोड यूनिट हैं? उनकी जगह वर्णों की गिनती या UTF-8 बाइटों की गिनती न करें।
- इच्छित परिणाम जाँचें। क्या आप जोड़ा गया सटीक योगदान वापस पाना चाहते हैं या पूरा प्रदर्शित वर्ण चुनना चाहते हैं? दोनों के लिए अलग सीमाएँ आवश्यक हो सकती हैं।
वर्ण-केंद्रित चयन हमेशा केवल एक जोड़े गए योगदान को दर्शा नहीं सकता। स्वर-चिह्न, पिछले अक्षर के साथ उसी प्रदर्शित वर्ण का हिस्सा है। अगर डेस्कटॉप टास्क में हाइलाइट करना है, तो प्रस्तुति का स्पष्ट नियम चुनें: चयन को उस वर्ण तक फैलाएँ जिसमें यह आता है, या योगदान को अलग खंड सूची में दिखाएँ। फैलाने पर पहले के खंड का पाठ भी शामिल होता है, इसलिए सटीक पुनर्प्राप्ति के लिए मूल ऑफ़सेट सुरक्षित रखें।
सटीक जोड़-ट्रैकिंग के लिए उपयोगी अपरिवर्तनीयता सरल है: प्रीफ़िक्स को अपरिवर्तित रखें, निर्देशांक की इकाइयाँ बताएँ और हर स्पैन को उसके पाठ के साथ रखें। इस अनुबंध के अंतर्गत लेजर किसी एक String.Index को म्यूटेशन के पार सँभाले बिना, बाद के जोड़ के बाद पहले के योगदान वापस पा सकती है।
स्रोत
Collection — Apple।
Strings and Characters — Swift.org।
String — Apple।
NSString — Apple।
append(contentsOf:) — Apple।
substring(with:) — Apple।
length — Apple।
Concurrency — Swift.org।
Sendable — Swift.org।