Saving/writing swift structs as a file or binary format - swift

I have a Swift struct of Int32, floats and data and I want to save this struct instance in a binary format - Can someone help me on how to write or save file in the binary format?
struct Another {
let imgCount: UInt32
let resetFlag: UInt32
let focalX: Float
let focalY: Float
let somedata: Data
}
Also the MemoryLayout<Another>.size and MemoryLayout<Another>.stripe is confusing.

You will need to reserve the first 16 bytes (4 bytes for each numeric property because they are all 32 bits). The tail of the data will be used for the data property someData which can be of any size:
struct Another {
// UInt32 - A 32-bit unsigned integer value type. You will need 4 bytes for each property
// 4 + 4 = 8 Bytes
let imgCount, resetFlag: UInt32
// Float - A single-precision, floating-point value type. Again 4 bytes for each property
// 4 + 4 = 8 Bytes
let focalX, focalY: Float
// the data can have any size this will be all bytes from index 16 until the end of the file
let someData: Data
}
To convert the numeric types to Data and back you can use the following helpers:
extension Numeric {
var data: Data {
var source = self
return Data(bytes: &source, count: MemoryLayout<Self>.size)
}
}
extension Data {
func object<T>() -> T { withUnsafeBytes { $0.load(as: T.self) } }
}
Now you just need to create a custom initializer to decode your data iterating over your data and set the correspond properties values.
extension Another {
init(_ data: Data) {
var startIndex = data.startIndex
var endIndex = startIndex.advanced(by: MemoryLayout<UInt32>.size)
let imgCountRange = startIndex..<endIndex
self.imgCount = data[imgCountRange].object()
startIndex = endIndex
endIndex = startIndex.advanced(by: MemoryLayout<UInt32>.size)
let resetFlagRange = startIndex..<endIndex
self.resetFlag = data[resetFlagRange].object()
startIndex = endIndex
endIndex = startIndex.advanced(by: MemoryLayout<Float>.size)
let focalXRange = startIndex..<endIndex
self.focalX = data[focalXRange].object()
startIndex = endIndex
endIndex = startIndex.advanced(by: MemoryLayout<Float>.size)
let focalYRange = startIndex..<endIndex
self.focalY = data[focalYRange].object()
self.someData = data[endIndex...]
}
}
To encode your structure will be much easier:
extension Another {
var data: Data {
return imgCount.data + resetFlag.data + focalX.data + focalY.data + someData
}
}
Testing:
let obj1 = Another(imgCount: 1, resetFlag: 2, focalX: 3.4, focalY: 5.6, someData: Data([0,1,2,3,4,5,6,7,8,9,10]))
let obj2 = Another(imgCount: 3, resetFlag: 4, focalX: 5.6, focalY: 7.8, someData: Data([10,11,12,13,14,15,16,17,18,19,20,21,22]))
let obj1Data = obj1.data
print(obj1Data) // 27 bytes
let obj1FromData = Another(obj1Data)
print(obj1FromData)
let obj2Data = obj2.data
print(obj2Data) // 29 bytes
let obj2FromData = Another(obj2Data)
print(obj2FromData)
This will print
27 bytes
Another(imgCount: 1, resetFlag: 2, focalX: 3.4, focalY: 5.6,
someData: 11 bytes)
29 bytes
Another(imgCount: 3, resetFlag: 4,
focalX: 5.6, focalY: 7.8, someData: 13 bytes)

Related

Convert Int to Array of UInt8 in swift

I want to convert a standard integer in a list of UInt8 in swift.
var x:Int = 2019
2019 can be written (for example) in hexadecimal 7E3 so i want some kind of function that converts is to a list of UInt8s which looks like this.
var y:[Uint8] = [0x07, 0xE3]
I already found this: Convert integer to array of UInt8 units but he/she is convertign the ascii symbols of the number not the number itself. So his example 94887253 should give a list like [0x05, 0xA7, 0xDD, 0x55].
In the best case the function i'm looking for has some kind of usage so that i can also choose the minimum length of the resulting array so that for example
foo(42, length:2) -> [0x00, 0x2A]
or
foo(42, length:4) -> [0x00, 0x00, 0x00, 0x2A]
You could do it this way:
let x: Int = 2019
let length: Int = 2 * MemoryLayout<UInt8>.size //You could specify the desired length
let a = withUnsafeBytes(of: x) { bytes in
Array(bytes.prefix(length))
}
let result = Array(a.reversed()) //[7, 227]
Or more generally, we could use a modified version of this snippet:
func bytes<U: FixedWidthInteger,V: FixedWidthInteger>(
of value : U,
to type : V.Type,
droppingZeros: Bool
) -> [V]{
let sizeInput = MemoryLayout<U>.size
let sizeOutput = MemoryLayout<V>.size
precondition(sizeInput >= sizeOutput, "The input memory size should be greater than the output memory size")
var value = value
let a = withUnsafePointer(to: &value, {
$0.withMemoryRebound(
to: V.self,
capacity: sizeInput,
{
Array(UnsafeBufferPointer(start: $0, count: sizeInput/sizeOutput))
})
})
let lastNonZeroIndex =
(droppingZeros ? a.lastIndex { $0 != 0 } : a.indices.last) ?? a.startIndex
return Array(a[...lastNonZeroIndex].reversed())
}
let x: Int = 2019
bytes(of: x, to: UInt8.self, droppingZeros: true) // [7, 227]
bytes(of: x, to: UInt8.self, droppingZeros: false) // [0, 0, 0, 0, 0, 0, 7, 227]

UInt32 array to String Byte Array in Swift

Before I start I would like to apologise if I say something crazy.
I am working on an app that implements a c library. Among others, It shares idArrays.
I have the part decodes an idArray and it was given to me:
func decodeArrayID(aArray:UnsafeMutablePointer<CChar>, aTokenLen:UInt32)->([UInt32], String){
let arrayCount = Int(aTokenLen / 4)
var idArrayTemp = [UInt32]()
var idArrayStringTemp = ""
for i in 0..<arrayCount{
let idValue = decodeArrayIDItem(index: i, array: aArray)
idArrayTemp.append(idValue)
idArrayStringTemp += "\(idValue) "
}
return (idArrayTemp, idArrayStringTemp)
}
func decodeArrayIDItem(index:Int, array:UnsafeMutablePointer<CChar>) -> UInt32{
var value:UInt32 = UInt32(array[index * 4]) & 0xFF
value <<= 8
value |= UInt32(array [index * 4 + 1]) & 0xFF
value <<= 8
value |= UInt32(array [index * 4 + 2]) & 0xFF
value <<= 8
value |= UInt32(array [index * 4 + 3]) & 0xFF
return value
}
As we can see the idArray is send through UnsafeMutablePointer AKA UnsafeMutablePointer.
Now I am working with the encoding part. The function will take an array of UInt32 values and will try to convert it into byte array and will convert into a sting for sending it through the library.
So far I have the following code but it doesn't work:
func encodeIDArray(idArray:[UInt32])->String{
var aIDArray8:[UInt8] = [UInt8]()
for var value in idArray{
let count = MemoryLayout<UInt32>.size
let bytePtr = withUnsafePointer(to: &value) {
$0.withMemoryRebound(to: UInt8.self, capacity: count) {
UnsafeBufferPointer(start: $0, count: count)
}
}
aIDArray8 += Array(bytePtr)
}
let stringTest = String(data: Data(aIDArray8), encoding: .utf8)
return stringTest!
}
A test result for the input [1,2] returns "\u{01}\0\0\0\u{02}\0\0\0" and something tells is not quite right...
Thank you
Edited
The c functions are
DllExport void STDCALL DvProviderAvOpenhomeOrgPlaylist1EnableActionIdArray(THandle aProvider, CallbackPlaylist1IdArray aCallback, void* aPtr);
where CallbackPlaylist1IdArray is
typedef int32_t (STDCALL *CallbackPlaylist1IdArray)(void* aPtr, IDvInvocationC* aInvocation, void* aInvocationPtr, uint32_t* aToken, char** aArray, uint32_t* aArrayLen);
and the value to aArray is the value that get the Byte array
I believe you are in the right way
func encodeIDArray(idArray:[UInt32])->String{
var aIDArray8:[UInt8] = [UInt8]()
for var value in idArray{
let count = MemoryLayout<UInt32>.size
let bytePtr = withUnsafePointer(to: &value) {
$0.withMemoryRebound(to: UInt8.self, capacity: count) { v in
//Just change it to don't return the pointer itself, but the result of the rebound
UnsafeBufferPointer(start: v, count: count)
}
}
aIDArray8 += Array(bytePtr)
}
let stringTest = String(data: Data(aIDArray8), encoding: .utf8)
return stringTest!
}
Change your test to a some valid value in ASCII Table like this
encodeIDArray(idArray: [65, 66, 67]) // "ABC"
I hope it help you... Good luck and let me know it it works on your case.
You can copy the [UInt32] array values to the allocated memory without creating an intermediate [Int8] array, and use the bigEndian
property instead of bit shifting and masking:
func writeCArrayValue(from pointer:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?,
withUInt32Values array: [UInt32]){
pointer?.pointee = UnsafeMutablePointer<Int8>.allocate(capacity: MemoryLayout<UInt32>.size * array.count)
pointer?.pointee?.withMemoryRebound(to: UInt32.self, capacity: array.count) {
for i in 0..<array.count {
$0[i] = array[i].bigEndian
}
}
}
In the same way you can do the decoding:
func decodeArrayID(aArray:UnsafeMutablePointer<CChar>, aTokenLen:UInt32)->[UInt32] {
let arrayCount = Int(aTokenLen / 4)
var idArrayTemp = [UInt32]()
aArray.withMemoryRebound(to: UInt32.self, capacity: arrayCount) {
for i in 0..<arrayCount {
idArrayTemp.append(UInt32(bigEndian: $0[i]))
}
}
return idArrayTemp
}
You can't convert a binary buffer to a string and expect it to work. You should base64 encode your binary data. That IS a valid way to represent binary data as strings.
Consider the following code:
//Utility function that takes a typed pointer to a data buffer an converts it to an array of the desired type of object
func convert<T>(count: Int, data: UnsafePointer<T>) -> [T] {
let buffer = UnsafeBufferPointer(start: data, count: count);
return Array(buffer)
}
//Create an array of UInt32 values
let intArray: [UInt32] = Array<UInt32>(1...10)
print("source arrray = \(intArray)")
let arraySize = MemoryLayout<UInt32>.size * intArray.count
//Convert the array to a Data object
let data = Data(bytes: UnsafeRawPointer(intArray),
count: arraySize)
//Convert the binary Data to base64
let base64String = data.base64EncodedString()
print("Array as base64 data = ", base64String)
if let newData = Data(base64Encoded: base64String) {
newData.withUnsafeBytes { (bytes: UnsafePointer<UInt32>)->Void in
let newArray = convert(count:10, data: bytes)
print("After conversion, newArray = ", newArray)
}
} else {
fatalError("Failed to base-64 decode data!")
}
The output of that code is:
source arrray =[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Array as base64 data = AQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAA==
After conversion, newArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Program ended with exit code: 0
Although I really appreciate all the answers I have finally figured out what was happening. I have to say that Duncan's answer was the closest to my problem.
So far I have interpreted char** as String. Turns out that it can be also a pointer to an array (Correct me if I am Wrong!). Converting the array as String gave a format that the library didn't like and it could not be decode on the other end.
The way I ended up doing is:
func encodeIDArray(idArray:[UInt32])->[Int8]{
var aIDArray8 = [UInt8].init(repeating: 0, count: idArray.count*4)
for i in 0..<idArray.count{
aIDArray8[i * 4] = UInt8(idArray[i] >> 24) & 0xff
aIDArray8[i * 4 + 1] = UInt8(idArray[i] >> 16) & 0xff
aIDArray8[i * 4 + 2] = UInt8(idArray[i] >> 8) & 0xff
aIDArray8[i * 4 + 3] = UInt8(idArray[i]) & 0xff
}
return aIDArray8.map { Int8(bitPattern: $0) }
}
and then I am assigning the value of the C Variable in swift like that:
let myArray = encodeIDArray(idArray:theArray)
writeCArrayValue(from: aArrayPointer, withValue: myArray)
func writeCArrayValue(from pointer:UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?, withValue array:[Int8]){
pointer?.pointee = UnsafeMutablePointer<Int8>.allocate(capacity: array.count)
memcpy(pointer?.pointee, array, array.count)
}
aArrayPointer is a the char** used by the library.

How to convert my bytes data to Hex String and then Signed integer (32-bit) Two's complement from that ..?

I have a data like a bellow:
let data = Data(bytes: [206, 66, 49, 62])
Then I used this extension (from How to convert Data to hex string in swift) to convert to a hex string:
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * count)
for byte in self {
chars.append(hexDigits[Int(byte / 16)])
chars.append(hexDigits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
And then it is giving "ce42313e" as hex string. Now I am trying to convert this to Signed integer (32-bit) Two's complement .. I tried a couple of ways but not find anything perfectly.
When I give "ce42313e" in this bellow link under hex decimal the value is -834522818
http://www.binaryconvert.com/convert_signed_int.html
bellow is one of those I tried to convert "ce42313e" to int and it's giving me 3460444478 ..instead of -834522818 .
let str = value
let number = Int(str, radix: 16)
Please help out to get that value.
Int(str, radix: 16) interprets the string as the hexadecimal
representation of an unsigned number. You could convert it to
Int32 with
let data = Data(bytes: [206, 66, 49, 62])
let str = data.hexEncodedString()
print(str) // ce42313e
let number = Int32(truncatingBitPattern: Int(str, radix: 16)!)
print(number) // -834522818
But actually you don't need the hex representation for that purpose.
Your data is the big-endian representation of a signed 32-bit integer,
and this is how you can get the number from the data directly:
let data = Data(bytes: [206, 66, 49, 62])
let number = Int32(bigEndian: data.withUnsafeBytes { $0.pointee })
print(number) // -834522818

NSData to [Uint8] in Swift

I couldn't find a solution to this problem in Swift (all of them are Objective-C, and they deal with pointers which I don't think exist in Swift in the same form). Is there any way to convert a NSData object into an array of bytes in the form of [Uint8] in Swift?
You can avoid first initialising the array to placeholder values, if you go through pointers in a slightly convoluted manner, or via the new Array constructor introduced in Swift 3:
Swift 3
let data = "foo".data(using: .utf8)!
// new constructor:
let array = [UInt8](data)
// …or old style through pointers:
let array = data.withUnsafeBytes {
[UInt8](UnsafeBufferPointer(start: $0, count: data.count))
}
Swift 2
Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(data.bytes), count: data.length))
Swift 5 Solution
Data to [bytes]
extension Data {
var bytes: [UInt8] {
return [UInt8](self)
}
}
[bytes] to Data
extension Array where Element == UInt8 {
var data: Data {
return Data(self)
}
}
It's funny but exist more simple solution. Works in Swift 3. Surely. I've used this today.
data: Data // as function parameter
let byteArray = [UInt8](data)
That's all! :)
NSData easily bridged to Data.
UPDATE: (due to Andrew Koster comment)
Swift 4.1, Xcode 9.3.1
Just has been rechecked - all works as expected.
if let nsData = NSData(base64Encoded: "VGVzdFN0cmluZw==", options: .ignoreUnknownCharacters) {
let bytes = [UInt8](nsData as Data)
print(bytes, String(bytes: bytes, encoding: .utf8))
Output: [84, 101, 115, 116, 83, 116, 114, 105, 110, 103] Optional("TestString")
You can use the getBytes function of NSData to get the byte array equivalent.
As you did not provide any source code, I will use a Swift String contents that has been converted to NSData.
var string = "Hello World"
let data : NSData! = string.dataUsingEncoding(NSUTF8StringEncoding)
let count = data.length / sizeof(UInt8)
// create an array of Uint8
var array = [UInt8](count: count, repeatedValue: 0)
// copy bytes into array
data.getBytes(&array, length:count * sizeof(UInt8))
println(array)
Swift 3/4
let count = data.length / MemoryLayout<UInt8>.size
// create an array of Uint8
var byteArray = [UInt8](repeating: 0, count: count)
// copy bytes into array
data.getBytes(&byteArray, length:count)
Swift 3/4
let data = Data(bytes: [0x01, 0x02, 0x03])
let byteArray: [UInt8] = data.map { $0 }
You can try
extension Data {
func toByteArray() -> [UInt8]? {
var byteData = [UInt8](repeating:0, count: self.count)
self.copyBytes(to: &byteData, count: self.count)
return byteData
}
}
swift 4 and image data to a byte array.
func getArrayOfBytesFromImage(imageData:Data) ->[UInt8]{
let count = imageData.count / MemoryLayout<UInt8>.size
var byteArray = [UInt8](repeating: 0, count: count)
imageData.copyBytes(to: &byteArray, count:count)
return byteArray
}

How to byte reverse NSData output in Swift the littleEndian way?

I have this output from NSData: <00000100 84000c00 071490fe 4dfbd7e9>
So how could I byte reverse it in Swift and have this output: <00000001 0084000c 1407fe90 fb4de9d7>?
This should work to swap each pair of adjacent bytes in the data.
The idea is to interpret the bytes as an array of UInt16 integers
and use the built-in byteSwapped property.
func swapUInt16Data(data : NSData) -> NSData {
// Copy data into UInt16 array:
let count = data.length / sizeof(UInt16)
var array = [UInt16](count: count, repeatedValue: 0)
data.getBytes(&array, length: count * sizeof(UInt16))
// Swap each integer:
for i in 0 ..< count {
array[i] = array[i].byteSwapped // *** (see below)
}
// Create NSData from array:
return NSData(bytes: &array, length: count * sizeof(UInt16))
}
If your actual intention is to convert data from an (external)
big-endian representation to the host (native) byte order (which happens to be little-endian on all current iOS and OS X devices) then you should replace *** by
array[i] = UInt16(bigEndian: array[i])
Example:
var bytes : [UInt8] = [1, 2, 3, 4, 5, 6, 7, 8]
let data = NSData(bytes: &bytes, length: bytes.count)
print(data)
// <01020304 05060708>
print(swapUInt16Data(data))
// <02010403 06050807>
Update for Swift 3: The generic withUnsafeMutableBytes()
methods allows to obtain a UnsafeMutablePointer<UInt16> to the bytes
and modify them directly:
func swapUInt16Data(data : Data) -> Data {
var mdata = data // make a mutable copy
let count = data.count / MemoryLayout<UInt16>.size
mdata.withUnsafeMutableBytes { (i16ptr: UnsafeMutablePointer<UInt16>) in
for i in 0..<count {
i16ptr[i] = i16ptr[i].byteSwapped
}
}
return mdata
}
Example:
let data = Data(bytes: [1, 2, 3, 4, 5, 6, 7, 8])
print(data as NSData) // <01020304 05060708>
let swapped = swapUInt16Data(data: data)
print(swapped as NSData) // <02010403 06050807>
CoreFoundation has CFSwapInt32BigToHost and CFSwapInt32LittleToHost and other swap functions.
In swift3 it looks like this
struct FileHeader {
var magicNumber: UInt32 = 0
var count: UInt32 = 0
var width: UInt32 = 0
var height: UInt32 = 0
static func create(data: Data) -> FileHeader {
let structSize = MemoryLayout<FileHeader>.size
assert(data.count >= structSize)
var result = FileHeader()
let nsdata = data as NSData
nsdata.getBytes(&result, range: NSRange(location: 0, length: structSize))
result.magicNumber = CFSwapInt32BigToHost(result.magicNumber)
result.count = CFSwapInt32BigToHost(result.count)
result.width = CFSwapInt32BigToHost(result.width)
result.height = CFSwapInt32BigToHost(result.height)
return result
}
}
For someone may want to restrict the byte pattern, it would be a solution:
func swap<U:IntegerType>(data:NSData,_ :U.Type) -> NSData{
var length = data.length / sizeof(U)
var bytes = [U](count: length, repeatedValue: 0)
data.getBytes(&bytes, length: data.length)
// since byteSwapped isn't declare in any protocol, so we have do it by ourselves manually.
var inverse = bytes.enumerate().reduce([U](count: length, repeatedValue: 0)) { (var pre, ele) -> [U] in
pre[length - 1 - ele.index] = ele.element
return pre
}
return NSData(bytes: inverse, length: data.length)
}
for example:
swap(data:data,UInt8.self)
//before <0c20207b 17>
//after <177b2020 0c>
swap(data:anotherData,UInt16.self)
//before <8e004c01 84008f05 0701>
//after <07018f05 84004c01 8e00>