patternswiftMinor
Type to byte array conversion in Swift
Viewed 0 times
swiftconversionarraybytetype
Problem
I need the byte representation of a given type, especially
What do you think?
Especially I am interested in your opinion on
-
We can use the type inference and get rid of
but fails for
-
Is the following nicer? I don't think so, but what about you. :-)
-
What do you think about encapsulating the two function in a class, e.g.
-
What about giving
Update:
I created a ByteBackpacker project on github. Let me know what you think about it.
double, float, int8, etc. typealias Byte = UInt8
enum ByteOrder {
case BigEndian
case LittleEndian
}
func pack(var value: T, byteOrder: ByteOrder) -> [Byte] {
let valueByteArray = withUnsafePointer(&value) {
Array(UnsafeBufferPointer(start: UnsafePointer($0), count: sizeof(T)))
}
return (byteOrder == .LittleEndian) ? valueByteArray : valueByteArray.reverse()
}
func unpack(valueByteArray: [Byte], toType type: T.Type, byteOrder: ByteOrder) -> T {
let bytes = (byteOrder == .LittleEndian) ? valueByteArray : valueByteArray.reverse()
return UnsafePointer(bytes).memory
}What do you think?
Especially I am interested in your opinion on
- CFByteOrder does offer already a byte order, but I decided to go with a different definition.
-
We can use the type inference and get rid of
forType type: T.Type, which works fine for let result: double = unpack(someArray, .LittleEndian)but fails for
let result = unpack(someArray, .LittleEndian)-
Is the following nicer? I don't think so, but what about you. :-)
func unpack(valueByteArray: [Byte], byteOrder: ByteOrder) -> T {
let bytes = (byteOrder == .LittleEndian) ? valueByteArray : valueByteArray.reverse()
return bytes.withUnsafeBufferPointer {
return UnsafePointer($0.baseAddress).memory
}
}-
What do you think about encapsulating the two function in a class, e.g.
ByteBackPacker and convert them to class functions?-
What about giving
byteOrder: ByteOrder a default value, e.g. .LittleEndian, because this is the native byte order in iOS and Mac OS X, the primary platforms for Swift, I assume?Update:
I created a ByteBackpacker project on github. Let me know what you think about it.
Solution
Using a dedicated enumeration type
assign arbitrary integers to it, not just
If you don't pass the type in the unpack function then the compiler
has to infer it from the context and either of these would work:
If you pass the type as an argument then the compiler infers
the return type:
I think it is a matter of taste which version you choose, I would
prefer passing the type explicitly.
Encapsulating the functions into a type is also a good idea.
But I would use a
and make that instance methods and not class methods. The reason
is that you probably will do several calls with the same byte order
and you don't have to pass it to every call:
which can be used as
There is a problem with
in your first version of
elements of a Swift
as in your second version of
because
storage.
Your code assumes that the host byte order is little-endian,
which is the case for all current OS X and iOS platforms.
On the other hand, Swift is open source now and people already start
to port it to different platforms. I have no idea if Swift will be
ported to a big-endian platform at some time.
I would at least make that assumption explicit by defining
and then replace
by
In addition, I would add an assertion
e.g. to the
Alternatively, you can determine the actual host byte order at
runtime:
but I don't know if that is worth the hassle. (Note that
the static property
the lifetime of the app, not each time that it is used.)
Your generic pack/unpack functions can be called with
arguments of any type, and that would give wrong results or crashes for
use opaque pointers to the actual memory holding the elements.
There is – as far as I know – no protocol which comprises all integer and
floating point types. What you could do is to define a protocol
and make all types which can safely be packed and unpacked explicitly conform to it:
(Unfortunately, there are many integer types and I don't know if the repetition
can be avoided.)
Now you can restrict the type `
aut
ByteOrder is a good idea.CFByteOrder is (ultimately) a type alias for Int, so you canassign arbitrary integers to it, not just
CFByteOrderBig/LittleEndian.If you don't pass the type in the unpack function then the compiler
has to infer it from the context and either of these would work:
let d1 = unpack(bytes, byteOrder: .BigEndian) as Double
let d2 : Double = unpack(bytes, byteOrder: .BigEndian)If you pass the type as an argument then the compiler infers
the return type:
let d3 = unpack(bytes, toType: Double.self, byteOrder: .BigEndian)I think it is a matter of taste which version you choose, I would
prefer passing the type explicitly.
Encapsulating the functions into a type is also a good idea.
But I would use a
struct which is initialized with the byte order,and make that instance methods and not class methods. The reason
is that you probably will do several calls with the same byte order
and you don't have to pass it to every call:
struct BytePacker {
let byteOrder : ByteOrder
init (byteOrder: ByteOrder) {
self.byteOrder = byteOrder
}
func pack(var value: T) -> [Byte] { ... }
func unpack(valueByteArray: [Byte], toType type: T.Type) -> T { ... }
}which can be used as
let packer = BytePacker(byteOrder: .BigEndian)
let bytes = packer.pack(12.34)
let d = packer.unpack(bytes, toType: Double.self)There is a problem with
return UnsafePointer(bytes).memoryin your first version of
unpack() because is not guaranteed that theelements of a Swift
Array are stored in contiguous memory.return bytes.withUnsafeBufferPointer {
return UnsafePointer($0.baseAddress).memory
}as in your second version of
unpack() is the correct solutionbecause
withUnsafeBufferPointer() calls the block with a pointer to contiguousstorage.
Your code assumes that the host byte order is little-endian,
which is the case for all current OS X and iOS platforms.
On the other hand, Swift is open source now and people already start
to port it to different platforms. I have no idea if Swift will be
ported to a big-endian platform at some time.
I would at least make that assumption explicit by defining
enum ByteOrder {
case BigEndian
case LittleEndian
static var hostByteOrder = ByteOrder.LittleEndian
}and then replace
return (byteOrder == .LittleEndian) ? valueByteArray : valueByteArray.reverse()by
return (byteOrder == ByteOrder.hostByteOrder) ? valueByteArray : valueByteArray.reverse()In addition, I would add an assertion
assert(UInt(littleEndian: 1) == 1)e.g. to the
init method of BytePacker.Alternatively, you can determine the actual host byte order at
runtime:
enum ByteOrder {
case BigEndian
case LittleEndian
static var hostByteOrder = ByteOrder()
init() {
self = (UInt(littleEndian: 1) == 1) ? .LittleEndian : .BigEndian
}
}but I don't know if that is worth the hassle. (Note that
the static property
hostByteOrder is computed only once in the lifetime of the app, not each time that it is used.)
Your generic pack/unpack functions can be called with
arguments of any type, and that would give wrong results or crashes for
- reference types, where the instance is just a pointer to the object,
- "complex types" like
ArrayorString, which are astructbut
use opaque pointers to the actual memory holding the elements.
There is – as far as I know – no protocol which comprises all integer and
floating point types. What you could do is to define a protocol
protocol PackableType { }and make all types which can safely be packed and unpacked explicitly conform to it:
extension Int : PackableType { }
extension UInt16 : PackableType { }
// More integer types ...
extension Double : PackableType { }
// More floating point types ...(Unfortunately, there are many integer types and I don't know if the repetition
can be avoided.)
Now you can restrict the type `
to .
Alternatively, take a different approach and make the pack/unpack
methods operate on the type itself by defining a protocol extension:
extension PackableType {
func pack(byteOrder byteOrder: ByteOrder) -> [Byte] {
var value = self
let valueByteArray = withUnsafePointer(&value) {
Array(UnsafeBufferPointer(start: UnsafePointer($0), count: sizeofValue(value)))
}
return (byteOrder == ByteOrder.hostByteOrder) ? valueByteArray : valueByteArray.reverse()
}
static func unpack(valueByteArray: [Byte], byteOrder: ByteOrder) -> Self {
let bytes = (byteOrder == ByteOrder.hostByteOrder) ? valueByteArray : valueByteArray.reverse()
return bytes.withUnsafeBufferPointer {
return UnsafePointer($0.baseAddress).memory
}
}
}
(Note how the compiler infers the type in return UnsafePointer($0.baseAddress).memory`aut
Code Snippets
let d1 = unpack(bytes, byteOrder: .BigEndian) as Double
let d2 : Double = unpack(bytes, byteOrder: .BigEndian)let d3 = unpack(bytes, toType: Double.self, byteOrder: .BigEndian)struct BytePacker {
let byteOrder : ByteOrder
init (byteOrder: ByteOrder) {
self.byteOrder = byteOrder
}
func pack<T>(var value: T) -> [Byte] { ... }
func unpack<T>(valueByteArray: [Byte], toType type: T.Type) -> T { ... }
}let packer = BytePacker(byteOrder: .BigEndian)
let bytes = packer.pack(12.34)
let d = packer.unpack(bytes, toType: Double.self)return UnsafePointer<T>(bytes).memoryContext
StackExchange Code Review Q#114730, answer score: 7
Revisions (0)
No revisions yet.