Not: Kod şu anda Swift 5 (Xcode 10.2) için güncellendi . (Swift 3 ve Swift 4.2 sürümleri düzenleme geçmişinde bulunabilir.) Ayrıca muhtemelen hizalanmamış veriler artık doğru şekilde işleniyor.
Data
Bir değerden nasıl yaratılır
Swift 4.2'den itibaren, veriler basitçe bir değerden oluşturulabilir:
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData)
Açıklama:
withUnsafeBytes(of: value)
Kapanışı, değerin ham baytlarını kapsayan bir tampon işaretçisi ile çağırır.
- Ham tampon işaretçisi bir bayt dizisidir, bu nedenle
Data($0)
verileri oluşturmak için kullanılabilir.
Bir değer nasıl alınır Data
Swift 5'ten itibaren, withUnsafeBytes(_:)
of Data
, kapanışı UnsafeMutableRawBufferPointer
baytlara "türsüz" olarak çağırır . load(fromByteOffset:as:)
Yöntem, hafızadan değerini okur:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value)
Bu yaklaşımla ilgili bir sorun var: Belleğin tür için özellik hizalı olmasını gerektirir (burada: 8 baytlık bir adrese hizalı). Ancak bu garanti edilmez, örneğin veriler başka bir Data
değerden bir dilim olarak elde edilmişse .
Bu nedenle baytları şu değere kopyalamak daha güvenlidir :
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)
Açıklama:
Dönüş değeri, copyBytes()
kopyalanan bayt sayısıdır. Hedef arabelleğin boyutuna eşittir veya veriler yeterli bayt içermiyorsa daha azdır.
Genel çözüm 1
Yukarıdaki dönüşümler artık aşağıdaki genel yöntemler olarak kolayca uygulanabilir 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
}
}
Kısıtlama T: ExpressibleByIntegerLiteral
buraya eklenir, böylece değeri kolayca "sıfır" olarak başlatabiliriz - bu gerçekten bir kısıtlama değildir, çünkü bu yöntem yine de "üç değerlikli" (tam sayı ve kayan nokta) türlerle kullanılabilir, aşağıya bakın.
Misal:
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")
}
Benzer şekilde, dönüştürmek diziler için Data
ve arka:
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
}
}
Misal:
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)
Genel çözüm # 2
Yukarıdaki yaklaşımın bir dezavantajı vardır: Aslında sadece tamsayılar ve kayan nokta türleri gibi "önemsiz" türlerle çalışır. "Karmaşık" türleri gibi Array
ve String
(gizli) olması altında yatan depolama işaretçiler ve sadece yapı kendisi kopyalayarak etrafına geçirilen edilemez. Ayrıca sadece gerçek nesne depolamasına işaret eden referans türleriyle de çalışmaz.
Öyleyse bu sorunu çözebilirsiniz.
Data
Ve geri dönüştürme yöntemlerini tanımlayan bir protokol tanımlayın :
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
Dönüşümleri bir protokol uzantısında varsayılan yöntemler olarak uygulayın:
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) }
}
}
Burada, sağlanan bayt sayısının türün boyutuyla eşleşip eşleşmediğini kontrol eden bir kullanılabilir başlatıcı seçtim .
Ve son olarak, güvenle Data
ve geri dönüştürülebilecek tüm türlere uygunluğu beyan edin :
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
Bu, dönüşümü daha da zarif hale getirir:
let value = 42.13
let data = value.data
print(data as NSData)
if let roundtrip = Double(data: data) {
print(roundtrip)
}
İkinci yaklaşımın avantajı, yanlışlıkla güvenli olmayan dönüşümler yapamayacağınızdır. Dezavantajı, tüm "güvenli" türleri açıkça listelemeniz gerektiğidir.
Protokolü, önemsiz olmayan bir dönüştürme gerektiren diğer türler için de uygulayabilirsiniz, örneğin:
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
return Data(self.utf8)
}
}
veya gerekli olan her şeyi yapmak için dönüştürme yöntemlerini kendi türlerinize uygulayın, böylece bir değeri serileştirin ve serisini kaldırın.
Bayt sırası
Yukarıdaki yöntemlerde bayt sırası dönüşümü yapılmaz, veriler her zaman ana bilgisayar bayt sırasındadır. Platformdan bağımsız bir gösterim için (ör. "Big endian" aka "ağ" bayt sırası), karşılık gelen tam sayı özelliklerini kullanın. başlatıcılar. Örneğin:
let value = 1000
let data = value.bigEndian.data
print(data as NSData)
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip))
}
Elbette bu dönüştürme genel olarak jenerik dönüştürme yöntemiyle de yapılabilir.