หมายเหตุ:ตอนนี้รหัสได้รับการอัปเดตสำหรับSwift 5 (Xcode 10.2) แล้ว (เวอร์ชัน Swift 3 และ Swift 4.2 สามารถพบได้ในประวัติการแก้ไข) ข้อมูลที่ไม่ตรงแนวอาจได้รับการจัดการอย่างถูกต้อง
วิธีสร้างData
จากมูลค่า
ใน Swift 4.2 ข้อมูลสามารถสร้างขึ้นจากค่าเพียงด้วย
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData)
คำอธิบาย:
วิธีดึงค่าจาก Data
ในฐานะของสวิฟท์ 5 withUnsafeBytes(_:)
ของData
จะเรียกปิดด้วย“untyped” UnsafeMutableRawBufferPointer
เพื่อไบต์ load(fromByteOffset:as:)
วิธีอ่านค่าจากหน่วยความจำ:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value)
มีปัญหาอย่างหนึ่งในแนวทางนี้: ต้องการให้หน่วยความจำมีคุณสมบัติที่สอดคล้องกับประเภท (ที่นี่: จัดชิดกับที่อยู่ 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)
คำอธิบาย:
ค่าส่งคืนของ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
เพิ่มข้อ จำกัดที่นี่เพื่อให้เราสามารถเริ่มต้นค่าเป็น "ศูนย์" ได้อย่างง่ายดายนั่นไม่ใช่ข้อ จำกัด เพราะวิธีนี้สามารถใช้กับประเภท "trival" (จำนวนเต็มและทศนิยม) ได้ดูด้านล่าง
ตัวอย่าง:
let value = 42.13
let data = Data(from: value)
print(data as NSData)
if let roundtrip = data.to(type: Double.self) {
print(roundtrip)
} 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)
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip)
โซลูชันทั่วไป # 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 { }
สิ่งนี้ทำให้การแปลงสวยงามยิ่งขึ้น:
let value = 42.13
let data = value.data
print(data as NSData)
if let roundtrip = Double(data: data) {
print(roundtrip)
}
ข้อดีของแนวทางที่สองคือคุณไม่สามารถทำการแปลงที่ไม่ปลอดภัยโดยไม่ตั้งใจได้ ข้อเสียคือคุณต้องแสดงรายการประเภท "ปลอดภัย" ทั้งหมดอย่างชัดเจน
คุณยังสามารถใช้โปรโตคอลสำหรับประเภทอื่น ๆ ที่ต้องการการแปลงที่ไม่สำคัญเช่น:
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
return Data(self.utf8)
}
}
หรือใช้วิธีการแปลงในประเภทของคุณเองเพื่อทำสิ่งที่จำเป็นเพื่อทำให้เป็นอนุกรมและกำหนดค่าเริ่มต้นใหม่
คำสั่งไบต์
ไม่มีการแปลงลำดับไบต์ในวิธีการข้างต้นข้อมูลจะอยู่ในลำดับไบต์ของโฮสต์เสมอ สำหรับการแสดงที่เป็นอิสระจากแพลตฟอร์ม (เช่น "big endian" หรือที่เรียกว่า "network" byte order) ให้ใช้คุณสมบัติจำนวนเต็มที่สอดคล้องกัน initializers. ตัวอย่างเช่น:
let value = 1000
let data = value.bigEndian.data
print(data as NSData)
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip))
}
แน่นอนว่าการแปลงนี้สามารถทำได้โดยทั่วไปในวิธีการแปลงทั่วไป