Introduction
Swift provides the Data
structure as an easy way to deal with byte buffers (sometimes called byte arrays). this means it holds raw bytes. This allows for very fast transfer of data (like over bluetooth).
It may be a little tricky to use it with some data types. This post provides ways to use Data
to hold some of the most useful data types.
Just keep in mind that there has to be some kind of an "agreement" between your code and the consuming party of this data (it should know what data type and size are being sent).
If you are using a Swift playground, just import CoreData
before you begin.
Code
UInt8
(and maybe booleans)
This is the primitive byte type as mentioned in the Swift docs; it is a raw byte after all 😅. It is the most straightforward to use as well.
Data
can be used to hold an array of UInt8
s directly.
let data = Data([1, 5])
/* or maybe create a flag,
* change it in different places of the app
* and send it like so..
*/
var flag: UInt8 = 0
let data = Data([flag])
This can also be used as a boolean flag by using 0 and 1 as values for a single element array (like you've seen in the above example).
String
Strings are also pretty easy to deal with.
let myString = "Cats are awesome"
let data = string.data(using: .utf8)
Just note that depending on your encoding of choice, the size is defferent. For this example with utf8, every character is 1 byte.
Float
, Double
and other number types
Other number types can take advantage of the flexibility of the UnsafeRawPointer
to point directly to the memory location of the variable holding the data you want to send. This pointer is then used to initialize the Data
object.
var myDouble = -9.52
let data = withUnsafePointer(to: &myDouble) {
Data(bytes: $0, count: MemoryLayout.size(ofValue: myDouble))
}
This creates a Data
object with the first parameter being a pointer to the myDouble
variable.
The
$0
is Swift's shorthand name of theUnsafePointer
ofmyDouble
. Check this answer if you need more info.
The second argument is the length which the Data
initializer needs to walk through the memory to represent your variable, for this we have used the
MemoryLayout.size(ofValue:)
function to get the size of the myDouble
variable automatically (which we know is 8 bytes, or 64 bits).
So this basically says: Starting at the position of myDouble
in memory, walk 8 bytes, copy these bytes into a Data
object.
Multiple data types with the same Data
object
Most of the time, you would be okay with sending one type of data, but you might want to take advantage of the fact that you can send like double the size of the data you are sending at the moment to include other data.
Lets imagine that we want to send the price of a cat, along with a flag indicating if it is a kitten or a grown up cat.
// Like before, define a variable for the price
// and create a Data object with it
var price = 100.99
var data = withUnsafePointer(to: &price) {
Data(bytes: $0, count: MemoryLayout.size(ofValue: price))
}
// now, define the kitten flag
var kitten: UInt8 = 1
// Make a Data object with it
let kittenFlagData = Data([kitten])
// append the two objects
data.append(kittenFlagData)
data
now has 9 bytes, 8 bytes for the price (which, from the prespective of the consuming party, starts at offset 0).
It also has 1 byte for the flag (which, from the prespective of the consuming party, starts at offset 8).
That's it. Feel free to provide any kind of feedback.