I need to create create a data stream that contains multiple parameters, send it over the network and then extract those parameter when I receive the data. This is how and create my data (I'm certain all of my variables contain a value)
let dataToSend = NSMutableData()
var mType = Int32(messageType.rawValue)
var reqId = Int32(requestId)
dataToSend.appendDataWithUnsafeBytes(from: &mType, of: Int32.self)
dataToSend.appendDataWithUnsafeBytes(from: &reqId, of: Int32.self)
/* extra protocol data length. In version 0, this is0 as thereis no extra data.
In the future, if you need to add extra protocol data use this*/
var unUsedProtocol = Int32(0)
dataToSend.appendDataWithUnsafeBytes(from: &unUsedProtocol, of: Int32.self)
var encodedDataJson = !jsonString.isEmptyOrNil ? jsonString?.asciiValues : [UInt8]()
dataToSend.appendDataWithUnsafeBytes(from: &encodedDataJson, of: [UInt8]?.self)
var bData = bindaryData
dataToSend.appendDataWithUnsafeBytes(from: &bData, of: Data.self)
here is my appendDataWithUnsafeBytes NSMutableData extension.
extension NSMutableData {
func appendDataWithUnsafeBytes<T>(from element: inout T, of type: T.Type) {
let size = MemoryLayout.size(ofValue: element)
withUnsafeBytes(of: &element) { ptr in
let buffer = ptr.bindMemory(to: type)
if let address = buffer.baseAddress {
self.append(address, length: size)
} else {
VsLogger.logDebug("appendDataWithUnsafeBytes", "unable to get base address of pointer of type: \(type)")
}
}
}
}
and this is how try to extract it (I get the index value along with the data)
var messageTypeValue: Int32? = nil
var requestId: Int32? = nil
var encodedJsonData: Data? = nil
var binaryData: Data? = nil
let intSize = MemoryLayout<Int32>.size
let dataSize = MemoryLayout<Data>.size
var offset = index
bufferData.getBytes(&messageTypeValue, range: NSRange(location: offset, length: intSize))
offset += intSize //8
bufferData.getBytes(&requestId, range: NSRange(location: offset, length: intSize))
offset += intSize //12
/*skipping extra bytes (unsuedProtocol in sendMessageFunction). They come from a future version
that this code doesn't understand*/
offset += intSize //16
bufferData.getBytes(&encodedJsonData, range: NSRange(location: offset, length: dataSize))
offset += dataSize //32
bufferData.getBytes(&binaryData, range: NSRange(location: offset, length: dataSize))
I'm only able to get the first value (messageTypeValue) but for the rest I either get nil or not the right data.
Thank you!
***** UPDATE *****
I got it working by modifying my sending and receiving functions as follows. Where I send it.
let dataToSend = NSMutableData()
var mType = Int32(messageType.rawValue)
var reqId = Int32(requestId)
dataToSend.appendDataWithUnsafeBytes(from: &mType, of: Int32.self)
dataToSend.appendDataWithUnsafeBytes(from: &reqId, of: Int32.self)
/* estra protocol data length. In version 0, this is0 as thereis no extra data.
In the future, if you need to add extra protocol data use this*/
var unUsedProtocol = Int32(0)
dataToSend.appendDataWithUnsafeBytes(from: &unUsedProtocol, of: Int32.self)
var jsonData = Data(!jsonString.isEmptyOrNil ? jsonString!.asciiValues : [UInt8]())
dataToSend.appendDataWithUnsafeBytes(from: &jsonData, of: Data.self)
var bData = bindaryData
dataToSend.appendDataWithUnsafeBytes(from: &bData, of: Data.self)
where I receive it
var offset = Int(index)
let int32Size = MemoryLayout<Int32>.size
let dataSize = MemoryLayout<Data>.size
let messageTypeValue = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
let requestId = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
//skip this one since it not used
//let unusedProtocol = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
let encodedJsonData = (bufferData.bytes + offset).load(as: Data.self)
offset += dataSize
let binaryData = bufferData.bytes.load(fromByteOffset: offset, as: Data.self)
the data is supposed to always be align properly, but is there a way to do some error checking on bufferData.bytes.load(fromByteOffset:as:)
As already mentioned in the comments I highly recommend to use native Data
.
First of all you need MartinR's Data extension to convert numeric types to Data
and vice versa. I added an custom append
method
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
}
mutating func append<T>(_ other: T) {
append(.init(from: other))
}
}
This is a very simple version of your data set, three Int32
values and a JSON array
let mType : Int32 = 1
let reqId : Int32 = 2
let unUsedProtocol : Int32 = 0
let json = try! JSONEncoder().encode(["hello", "world"])
For convenience I omitted the error handling. In production code try!
is discouraged.
The huge benefit of Data
is that it can be treated as a collection type, an array of (UInt8
) bytes. To build the Data
package convert the numeric values and append the bytes respectively
var dataToSend = Data()
dataToSend.append(mType)
dataToSend.append(reqId)
dataToSend.append(unUsedProtocol)
dataToSend.append(json)
On the receiver side to extract the numeric values back this is a helper function which increments also the current index (as inout
type), the byte length arises from the type. The function throws
an error if the index is out of range and if the type cannot be converted.
enum ConvertDataError : Error { case outOfRange, invalidType}
func extractNumber<T : ExpressibleByIntegerLiteral>(from data : Data, type: T.Type, startIndex: inout Int) throws -> T {
let endIndex = startIndex + MemoryLayout<T>.size
guard endIndex <= data.endIndex else { throw ConvertDataError.outOfRange }
let subdata = data[startIndex..<endIndex]
guard let resultType = subdata.to(type: type) else { throw ConvertDataError.invalidType }
startIndex = endIndex
return resultType
}
Get the start index of the data package and extract the Int32
values
var index = dataToSend.startIndex
do {
let mType1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
let reqId1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
let unUsedProtocol1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
print(mType1, reqId1, unUsedProtocol1)
For the json part you need the length, in this example 17 bytes
let jsonLength = json.count
let jsonOffset = index
index += jsonLength
let json1 = try JSONDecoder().decode([String].self, from: dataToSend[jsonOffset..<index])
print(json1)
} catch {
print(error)
}