Есть несколько проблем с этим подходом:
let data = [UInt8](rowData) // rowData is type of Data class
let totalKM_BitsArray = [data[8], data[7], data[6], data[5], data[4]]
self.totalKm = UnsafePointer(totalKM_BitsArray)
.withMemoryRebound(to:UInt64.self, capacity: 1) { $0.pointee }
Разыменование UnsafePointer(totalKM_BitsArray)
равно неопределенное поведение , так как указатель на буфер totalKM_BitsArray
действителен только временно на время вызова инициализатора (возможно, в какой-то момент в будущем Swift будет предупреждать о таких конструкциях).
Вы пытаетесь привязать только 5 экземпляров UInt8
к UInt64
, поэтому оставшиеся 3 экземпляра будут мусором.
Вы можете только withMemoryRebound(_:)
между типами одинакового размера и шага; что не относится к UInt8
и UInt64
.
Это зависит от порядкового номера вашей платформы; data[8]
будет самым младшим байтом на платформе с прямым порядком байтов, но самым значимым байтом на платформе с прямым порядком байтов.
Ваша реализация со сдвигом битов позволяет избежать всех этих проблем (и, как правило, это более безопасный путь, поскольку вам не нужно учитывать такие вещи, как совместимость компоновки, выравнивание и наложение указателей).
Однако, если вы просто хотите дополнить свои данные нулями для самых старших байтов, с rowData[4]
до rowData[8]
, составляющими оставшиеся младшие байты, тогда вам потребуется сдвиг битов реализация должна выглядеть так:
let rowData = Data([
100, 200, 28, 155, 0, 0, 0, 26, 244, 0, 0, 0, 45, 69, 0, 0, 0, 4, 246
])
let byte0 = UInt64(rowData[4]) << 32
let byte1 = UInt64(rowData[5]) << 24
let byte2 = UInt64(rowData[6]) << 16
let byte3 = UInt64(rowData[7]) << 8
let byte4 = UInt64(rowData[8])
let totalKm = byte0 | byte1 | byte2 | byte3 | byte4
print(totalKm) // 6900
или, итеративно:
var totalKm: UInt64 = 0
for byte in rowData[4 ... 8] {
totalKm = (totalKm << 8) | UInt64(byte)
}
print(totalKm) // 6900
или, используя reduce(_:_:)
:
let totalKm = rowData[4 ... 8].reduce(0 as UInt64) { accum, byte in
(accum << 8) | UInt64(byte)
}
print(totalKm) // 6900
Мы можем даже абстрагировать это в расширение на Data
, чтобы упростить загрузку таких целых чисел фиксированной ширины:
enum Endianness {
case big, little
}
extension Data {
/// Loads the type `I` from the buffer. If there aren't enough bytes to
/// represent `I`, the most significant bits are padded with zeros.
func load<I : FixedWidthInteger>(
fromByteOffset offset: Int = 0, as type: I.Type, endianness: Endianness = .big
) -> I {
let (wholeBytes, spareBits) = I.bitWidth.quotientAndRemainder(dividingBy: 8)
let bytesToRead = Swift.min(count, spareBits == 0 ? wholeBytes : wholeBytes + 1)
let range = startIndex + offset ..< startIndex + offset + bytesToRead
let bytes: Data
switch endianness {
case .big:
bytes = self[range]
case .little:
bytes = Data(self[range].reversed())
}
return bytes.reduce(0) { accum, byte in
(accum << 8) | I(byte)
}
}
}
Мы проделываем здесь небольшую дополнительную работу для того, чтобы прочитать правильное количество байтов, а также иметь возможность обрабатывать как большие, так и младшие порядковые номера. Но теперь, когда мы написали это, мы можем просто написать:
let totalKm = rowData[4 ... 8].load(as: UInt64.self)
print(totalKm) // 6900
Обратите внимание, что до сих пор я предполагал, что Data
, который вы получаете, имеет нулевую индексацию. Это безопасно для приведенных выше примеров, но не обязательно безопасно в зависимости от того, откуда поступают данные (как это может быть фрагмент). Вы должны быть в состоянии сделать Data(someUnknownDataValue)
, чтобы получить нулевое индексированное значение данных, с которым вы можете работать, хотя, к сожалению, я не верю, что есть какая-либо документация, которая гарантирует это.
Чтобы обеспечить правильную индексацию произвольного значения Data
, вы можете определить следующее расширение, чтобы выполнить правильное смещение в случае, когда вы имеете дело со срезом:
extension Data {
subscript(offset offset: Int) -> Element {
get { return self[startIndex + offset] }
set { self[startIndex + offset] = newValue }
}
subscript<R : RangeExpression>(
offset range: R
) -> SubSequence where R.Bound == Index {
get {
let concreteRange = range.relative(to: self)
return self[startIndex + concreteRange.lowerBound ..<
startIndex + concreteRange.upperBound]
}
set {
let concreteRange = range.relative(to: self)
self[startIndex + concreteRange.lowerBound ..<
startIndex + concreteRange.upperBound] = newValue
}
}
}
То, что вы можете использовать, затем назовите, например, data[offset: 4]
или data[offset: 4 ... 8].load(as: UInt64.self)
.
Наконец, стоит отметить, что, хотя вы , возможно, могли бы реализовать это как повторную интерпретацию битов, используя Data
withUnsafeBytes(_:)
метод:
let rowData = Data([
100, 200, 28, 155, 0, 0, 0, 26, 244, 0, 0, 0, 45, 69, 0, 0, 0, 4, 246
])
let kmData = Data([0, 0, 0] + rowData[4 ... 8])
let totalKm = kmData.withUnsafeBytes { buffer in
UInt64(bigEndian: buffer.load(as: UInt64.self))
}
print(totalKm) // 6900
Это полагается на то, что буфер Data
выровнен на 64 бита, что не гарантируется. Вы получите ошибку времени выполнения при попытке загрузить смещенное значение, например:
let data = Data([0x01, 0x02, 0x03])
let i = data[1...].withUnsafeBytes { buffer in
buffer.load(as: UInt16.self) // Fatal error: load from misaligned raw pointer
}
Загружая вместо этого отдельные значения UInt8
и выполняя сдвиг битов, мы можем избежать таких проблем выравнивания (однако, если / когда UnsafeMutableRawPointer
поддерживает невыровненные нагрузки , это больше не будет проблемой).