राउंड ट्रिप स्विफ्ट नंबर टाइप / से डेटा के लिए


94

स्विफ्ट 3 के Dataबजाय की ओर झुकाव के साथ [UInt8], मैं यह पता लगाने की कोशिश कर रहा हूं कि विभिन्न वस्तुओं (UInt8, Double, Float, Int64, आदि) को डेटा ऑब्जेक्ट के रूप में एन्कोड / डीकोड करने के लिए सबसे कुशल / मुहावरेदार तरीका क्या है।

नहीं है [UInt8] प्रयोग करने के लिए इस सवाल का जवाब है, लेकिन यह विभिन्न सूचक एपीआई का उपयोग किया है कि मैं डेटा पर नहीं मिल सकता है लगता है।

मैं मूल रूप से कुछ कस्टम एक्सटेंशन चाहते हैं जो कुछ इस तरह दिखते हैं:

let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13

वह हिस्सा जो मुझे वास्तव में परेशान करता है, मैंने डॉक्स के एक झुंड के माध्यम से देखा है, यह है कि मैं किसी भी मूल संरचना (जो सभी संख्याएं हैं) से किसी प्रकार की पॉइंटर चीज़ (OpaquePointer या BufferPointer या UnsafePointer?) प्राप्त कर सकते हैं। सी में, मैं इसके सामने एक एम्परसेंड को थप्पड़ मारूंगा, और फिर वहां जाऊंगा।


जवाबों:


256

नोट: कोड 5 स्विफ्ट के लिए अद्यतन किया गया है (Xcode 10.2) के । (स्विफ्ट 3 और स्विफ्ट 4.2 वर्जन को एडिट हिस्ट्री में पाया जा सकता है।) इसके अलावा संभवतः अनलगनेटेड डेटा को अब सही तरीके से हैंडल किया जा सकता है।

कैसे बनाएं Data एक मूल्य से

Swift 4.2 के अनुसार, डेटा को केवल एक मान से बनाया जा सकता है

let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }

print(data as NSData) // <713d0ad7 a3104540>

स्पष्टीकरण:

  • withUnsafeBytes(of: value) मूल्य के कच्चे बाइट्स को कवर करने वाले बफर पॉइंटर के साथ क्लोजर को आमंत्रित करता है।
  • एक कच्चा बफ़र पॉइंटर बाइट्स का एक क्रम है, इसलिए Data($0)इसका उपयोग डेटा बनाने के लिए किया जा सकता है।

कैसे से एक मान प्राप्त करने के लिए Data

स्विफ्ट 5 के रूप में, बाइट्स के लिए "अनपेड" ​​के साथ बंद करने withUnsafeBytes(_:)का Dataआह्वान किया UnsafeMutableRawBufferPointerload(fromByteOffset:as:)विधि स्मृति से मूल्य में लिखा है:

let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
    $0.load(as: Double.self)
}
print(value) // 42.13

इस दृष्टिकोण के साथ एक समस्या है: यह आवश्यक है कि स्मृति प्रकार के लिए संरेखित संपत्ति है (यहां: 8-बाइट पते के लिए संरेखित)। लेकिन इसकी गारंटी नहीं है, उदाहरण के लिए यदि डेटा दूसरे के एक स्लाइस के रूप में प्राप्त किया गया थाData मूल्य के ।

इसलिए यह मूल्य के लिए बाइट्स की प्रतिलिपि बनाना अधिक सुरक्षित है :

let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13

स्पष्टीकरण:

  • withUnsafeMutableBytes(of:_:) मूल्य के कच्चे बाइट्स को कवर करने वाले एक म्यूटेबल बफर पॉइंटर के साथ क्लोज को बंद करता है।
  • की copyBytes(to:)विधि DataProtocol(जिसके Dataअनुरूप) उस बफ़र के डेटा से बाइट्स कॉपी करता है।

का वापसी मूल्य copyBytes() कॉपी किए गए बाइट्स की संख्या है। यह गंतव्य बफ़र के आकार के बराबर है, या कम है यदि डेटा में पर्याप्त बाइट्स नहीं हैं।

सामान्य समाधान # 1

उपरोक्त रूपांतरण अब आसानी से सामान्य तरीकों के रूप में लागू किए जा सकते हैं struct Data:

extension Data {

    init<T>(from value: T) {
        self = Swift.withUnsafeBytes(of: value) { Data($0) }
    }

    func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
        var value: T = 0
        guard count >= MemoryLayout.size(ofValue: value) else { return nil }
        _ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
        return value
    }
}

बाधा T: ExpressibleByIntegerLiteral यहां इसलिए जोड़ी जाती है ताकि हम आसानी से "शून्य" के मूल्य को शुरू कर सकें - यह वास्तव में प्रतिबंध नहीं है क्योंकि इस पद्धति का उपयोग "ट्रिलिंग" (पूर्णांक और फ्लोटिंग पॉइंट) प्रकारों के साथ किया जा सकता है, वैसे भी नीचे देखें।

उदाहरण:

let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>

if let roundtrip = data.to(type: Double.self) {
    print(roundtrip) // 42.13
} else {
    print("not enough data")
}

इसी प्रकार, आप सरणियों को Dataवापस और पीछे बदल सकते हैं:

extension Data {

    init<T>(fromArray values: [T]) {
        self = values.withUnsafeBytes { Data($0) }
    }

    func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
        var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
        _ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
        return array
    }
}

उदाहरण:

let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>

let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]

सामान्य समाधान # 2

उपरोक्त दृष्टिकोण में एक नुकसान है: यह वास्तव में केवल "तुच्छ" प्रकार के साथ काम करता है जैसे पूर्णांक और फ्लोटिंग बिंदु प्रकार। "कॉम्प्लेक्स" प्रकार Array औरString अंतर्निहित मेमोरी के लिए (छिपे हुए) संकेत हैं और केवल संरचना की प्रतिलिपि बनाकर इसे पास नहीं किया जा सकता है। यह उन संदर्भ प्रकारों के साथ भी काम नहीं करेगा जो वास्तविक वस्तु भंडारण के लिए संकेत हैं।

तो उस समस्या को हल कर सकते हैं, एक कर सकते हैं

  • एक प्रोटोकॉल को परिभाषित करें जो परिवर्तित करने के लिए तरीकों को परिभाषित करता है Dataऔर पीछे:

    protocol DataConvertible {
        init?(data: Data)
        var data: Data { get }
    }
  • प्रोटोकॉल एक्सटेंशन में डिफ़ॉल्ट विधियों के रूप में रूपांतरणों को लागू करें:

    extension DataConvertible where Self: ExpressibleByIntegerLiteral{
    
        init?(data: Data) {
            var value: Self = 0
            guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
            _ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
            self = value
        }
    
        var data: Data {
            return withUnsafeBytes(of: self) { Data($0) }
        }
    }

    मैंने यहाँ एक फाल्ट इनिशियलाइज़र चुना है जो जाँचता है कि बाइट्स की संख्या टाइप के आकार से मेल खाती है।

  • और अंत में सभी प्रकारों के लिए अनुरूपता की घोषणा करें, जिन्हें सुरक्षित रूप से Dataऔर पीछे परिवर्तित किया जा सके :

    extension Int : DataConvertible { }
    extension Float : DataConvertible { }
    extension Double : DataConvertible { }
    // add more types here ...

यह रूपांतरण को और अधिक सुरुचिपूर्ण बनाता है:

let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>

if let roundtrip = Double(data: data) {
    print(roundtrip) // 42.13
}

दूसरे दृष्टिकोण का लाभ यह है कि आप अनजाने में रूपांतरण नहीं कर सकते हैं। नुकसान यह है कि आपको सभी "सुरक्षित" प्रकारों को स्पष्ट रूप से सूचीबद्ध करना होगा।

आप अन्य प्रकारों के लिए भी प्रोटोकॉल लागू कर सकते हैं, जिनके लिए गैर-तुच्छ रूपांतरण की आवश्यकता होती है, जैसे:

extension String: DataConvertible {
    init?(data: Data) {
        self.init(data: data, encoding: .utf8)
    }
    var data: Data {
        // Note: a conversion to UTF-8 cannot fail.
        return Data(self.utf8)
    }
}

या जो कुछ भी आवश्यक है उसे करने के लिए रूपांतरण विधियों को अपने स्वयं के प्रकारों में लागू करें ताकि मूल्य को क्रमबद्ध और निष्क्रिय कर सकें।

बाइट क्रम

उपरोक्त विधियों में कोई बाइट ऑर्डर रूपांतरण नहीं किया जाता है, डेटा हमेशा होस्ट बाइट क्रम में होता है। एक प्लेटफ़ॉर्म स्वतंत्र प्रतिनिधित्व के लिए (जैसे "बड़ा एंडियन" उर्फ ​​"नेटवर्क" बाइट ऑर्डर), इसी पूर्णांक गुण सम्मान का उपयोग करें। initializers। उदाहरण के लिए:

let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>

if let roundtrip = Int(data: data) {
    print(Int(bigEndian: roundtrip)) // 1000
}

बेशक यह रूपांतरण सामान्य रूप से सामान्य रूपांतरण विधि में भी किया जा सकता है।


क्या तथ्य यह है कि हमें varप्रारंभिक मूल्य की एक प्रति बनानी होगी, इसका मतलब है कि हम दो बार बाइट्स की नकल कर रहे हैं? मेरे वर्तमान उपयोग के मामले में, मैं उन्हें डेटा स्ट्रक्चर्स में बदल रहा हूं, इसलिए मैं appendउन्हें बाइट्स की बढ़ती धारा में बदल सकता हूं। सीधे सी में, यह जितना आसान है *(cPointer + offset) = originalValue। तो बाइट्स की नकल सिर्फ एक बार की जाती है।
ट्रैविस ग्रैग्स

1
@TravisGriggs: एक इंट या फ्लोट को कॉपी करना शायद सबसे अधिक प्रासंगिक नहीं होगा, लेकिन आप स्विफ्ट में इसी तरह की चीजें कर सकते हैं। यदि आपके पास एक है ptr: UnsafeMutablePointer<UInt8>तो आप संदर्भित मेमोरी को कुछ ऐसी चीज़ों के माध्यम से असाइन कर सकते हैं जैसे UnsafeMutablePointer<T>(ptr + offset).pointee = valueकि आपके स्विफ्ट कोड के साथ निकटता से मेल खाती है। एक संभावित समस्या है: कुछ प्रोसेसर केवल संरेखित मेमोरी एक्सेस की अनुमति देते हैं , जैसे कि आप एक इंट को विषम मेमोरी लोकेशन पर स्टोर नहीं कर सकते हैं। मुझे नहीं पता कि यह वर्तमान में प्रयुक्त इंटेल और एआरएम प्रोसेसर पर लागू होता है या नहीं।
मार्टिन आर।

1
@TravisGriggs: (cont'd) ... इसके अलावा यह आवश्यक है कि एक पर्याप्त बड़ी डेटा ऑब्जेक्ट पहले से ही बनाई गई है, और स्विफ्ट में आप केवल डेटा ऑब्जेक्ट बना और आरंभ कर सकते हैं, इसलिए आपके पास शून्य बाइट्स की एक अतिरिक्त प्रतिलिपि हो सकती है। प्रारंभ। - यदि आपको अधिक विवरण की आवश्यकता है तो मेरा सुझाव है कि आप एक नया प्रश्न पोस्ट करें।
मार्टिन आर

2
@ हंसबेंडे: मुझे डर है कि वर्तमान में यह संभव नहीं है। इसके लिए आवश्यकता होगी extension Array: DataConvertible where Element: DataConvertible। यह स्विफ्ट 3 में संभव नहीं है, लेकिन स्विफ्ट 4 (जहां तक ​​मुझे पता है) के लिए योजना बनाई गई है। Github.com/apple/swift/blob/master/docs/…
मार्टिन आर

1
@m_katsifarakis: यह है कि आप गलत टाइप हो सकता है Int.selfके रूप में Int.Type?
मार्टिन आर

3

आप का उपयोग करके उत्परिवर्तित वस्तुओं के लिए एक असुरक्षित सूचक प्राप्त कर सकते हैं withUnsafePointer:

withUnsafePointer(&input) { /* $0 is your pointer */ }

मैं अपरिवर्तनीय वस्तुओं के लिए एक पाने का तरीका नहीं जानता, क्योंकि इनऑउट ऑपरेटर केवल उत्परिवर्तित वस्तुओं पर काम करता है।

यह आपके द्वारा लिंक किए गए उत्तर में प्रदर्शित होता है।


2

मेरे मामले में, मार्टिन आर के जवाब ने मदद की लेकिन परिणाम उलटा था। इसलिए मैंने उनके कोड में एक छोटा सा बदलाव किया:

extension UInt16 : DataConvertible {

    init?(data: Data) {
        guard data.count == MemoryLayout<UInt16>.size else { 
          return nil 
        }
    self = data.withUnsafeBytes { $0.pointee }
    }

    var data: Data {
         var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario
         return Data(buffer: UnsafeBufferPointer(start: &value, count: 1))
    }
}

समस्या LittleEndian और BigEndian से संबंधित है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.