withUnsafeBytes' is deprecated: use `withUnsafeBytes<R>(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R` instead [duplicate] - swift

I previously used this code in Swift 4.2 to generate an id:
public static func generateId() throws -> UInt32 {
let data: Data = try random(bytes: 4)
let value: UInt32 = data.withUnsafeBytes { $0.pointee } // deprecated warning!
return value // + some other stuff
}
withUnsafeBytes is deprecated on Swift 5.0. How can I solve this?

In Swift 5 the withUnsafeBytes() method of Data calls the closure with an (untyped) UnsafeRawBufferPointer, and you can load() the value from the raw memory:
let value = data.withUnsafeBytes { $0.load(as: UInt32.self) }
(compare How to use Data.withUnsafeBytes in a well-defined manner? in the Swift forum). Note that this requires that the memory is aligned on a 4-byte boundary. For alternatives see round trip Swift number types to/from Data.
Note also that as of Swift 4.2 you can create a random 32-bit integer simply using the new Random API:
let randomId = UInt32.random(in: .min ... .max)

On Xcode 10.2, Swift 5, using $0.load(as:) didn't work for me, both when reading from the pointer or writing to it.
Instead, using $0.baseAddress?.assumingMemoryBound(to:) seems to work well.
Example reading from the pointer buffer (code is unrelated to the question):
var reachability: SCNetworkReachability?
data.withUnsafeBytes { ptr in
guard let bytes = ptr.baseAddress?.assumingMemoryBound(to: Int8.self) else {
return
}
reachability = SCNetworkReachabilityCreateWithName(nil, bytes)
}
Example writing to the buffer pointer (code is unrelated to the question):
try outputData.withUnsafeMutableBytes { (outputBytes: UnsafeMutableRawBufferPointer) in
let status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),
passphrase,
passphrase.utf8.count,
salt,
salt.utf8.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
rounds,
outputBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
kCCKeySizeAES256)
guard status == kCCSuccess else {
throw Error.keyDerivationError
}
}
The code from the question would look like:
let value = data.withUnsafeBytes {
$0.baseAddress?.assumingMemoryBound(to: UInt32.self)
}
In cases where the 'withUnsafeBytes' is deprecated: use withUnsafeBytes<R>(…) warning persists, it seems like the compiler can get confused when the closure has only one line. Making the closure have two or more lines might remove the ambiguity.

One more way to fix this warning to use bindMemory(to:).
var rawKey = Data(count: rawKeyLength)
let status = rawKey.withUnsafeMutableBytes { rawBytes -> Int32 in
guard let rawBytes = rawBytes.bindMemory(to: UInt8.self).baseAddress else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyUnwrap(alg, ivBytes, iv.count, keyBytes, key.count, wrappedKeyBytes, wrappedKey.count, rawBytes, &rawKeyLength)
}

I got this error as I was trying to figure out a compression stream tutorial. To get it to work, I added a step of converting the raw buffer pointer to a UnsafePointer
Original code from a tutorial I was working on.
--> where input: Data
--> where stream: compression_stream
//Method that shows the deprecation alert
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
//holder
var output = Data()
//Source and destination buffers
stream.src_ptr = srcPointer //UnsafePointer<UInt8>
stream.src_size = input.count
… etc.
}
Code with a conversion to make the above code work with a valid method
return input.withUnsafeBytes { bufferPtr in
//holder
var output = Data()
//Get the Raw pointer at the initial position of the UnsafeRawBuffer
let base: UnsafeRawPointer? = bufferPtr.baseAddress
//Unwrap (Can be combined with above, but kept it separate for clarity)
guard let srcPointer = base else {
return output
}
//Bind the memory to the type
let count = bufferPtr.count
let typedPointer: UnsafePointer<UInt8> = srcPointer.bindMemory(to: UInt8.self, capacity: count)
// Jump back into the original method
stream.src_ptr = typedPointer //UnsafePointer<UInt8>
}

Related

subdata method in Swift doesn't seem to be doing as I want

I have the following code in a playground (Swift 5)
import Foundation
let array : [UInt8] = [0,1,2,3,4,5,6,7,8,9,10,11,12]
public extension Data {
func uint32( offset:Int)-> UInt32{
let range = offset..<(offset+4)
let copy = self.subdata(in: range)
print(copy as NSData) // Prints <02030405>
return copy.withUnsafeBytes{
$0.load(fromByteOffset: 0, as: UInt32.self).bigEndian
}
}
}
let data = Data(array)
let datadropped = data.dropFirst(2)
print(data as NSData) // Prints <00010203 04050607 08090a0b 0c>
print(datadropped as NSData) // Prints <02030405 06070809 0a0b0c>
let sub = data.subdata(in: 4..<8 ) // gives 4,5,6,7
let sub2 = datadropped.subdata(in: 4..<8) // also gives 4,5,6,7
data.uint32(offset: 2)
Now if I set the offset in the final line as 0 or 1 it crashes. An offset of 2 works but returns a uint constructed using the bytes 02,03,04,05 which is not what I would expect. The documentation states the dropFirst() and subdata() return copies of the data.
I did get my uint32 function working with the following code. But I would like to know why the ranges of bytes in the initial function are not working. How do I force a genuine new copy of the Data? If someone could explain it to me I'd be grateful.
extension Data
func uint32( offset:Int)-> UInt32{
let array = Array(0...3).map {
uint8(offset: $0+ offset)
}
return array.withUnsafeBytes{
$0.load(fromByteOffset: 0, as: UInt32.self).bigEndian
}
}
func uint8( offset:Int)-> UInt8 {
return self.withUnsafeBytes{
$0.load(fromByteOffset: offset, as: UInt8.self).bigEndian
}
}
}
datadropped is a Slice
It contains the subset of the data but it shares the same indices with the original collection. It crashes because the first index of datadropped is 2, not 0.
To get a new Data object you have to write
let datadropped = Data(data.dropFirst(2))
For more information about slices please watch WWDC 2018: Using Collections Effectively (from 11:00)
Note: You can drop the fromByteOffset parameter
return copy.withUnsafeBytes{
$0.load(as: UInt32.self).bigEndian
}

Swift 5.0: 'withUnsafeBytes' is deprecated: use `withUnsafeBytes<R>(...)

I previously used this code in Swift 4.2 to generate an id:
public static func generateId() throws -> UInt32 {
let data: Data = try random(bytes: 4)
let value: UInt32 = data.withUnsafeBytes { $0.pointee } // deprecated warning!
return value // + some other stuff
}
withUnsafeBytes is deprecated on Swift 5.0. How can I solve this?
In Swift 5 the withUnsafeBytes() method of Data calls the closure with an (untyped) UnsafeRawBufferPointer, and you can load() the value from the raw memory:
let value = data.withUnsafeBytes { $0.load(as: UInt32.self) }
(compare How to use Data.withUnsafeBytes in a well-defined manner? in the Swift forum). Note that this requires that the memory is aligned on a 4-byte boundary. For alternatives see round trip Swift number types to/from Data.
Note also that as of Swift 4.2 you can create a random 32-bit integer simply using the new Random API:
let randomId = UInt32.random(in: .min ... .max)
On Xcode 10.2, Swift 5, using $0.load(as:) didn't work for me, both when reading from the pointer or writing to it.
Instead, using $0.baseAddress?.assumingMemoryBound(to:) seems to work well.
Example reading from the pointer buffer (code is unrelated to the question):
var reachability: SCNetworkReachability?
data.withUnsafeBytes { ptr in
guard let bytes = ptr.baseAddress?.assumingMemoryBound(to: Int8.self) else {
return
}
reachability = SCNetworkReachabilityCreateWithName(nil, bytes)
}
Example writing to the buffer pointer (code is unrelated to the question):
try outputData.withUnsafeMutableBytes { (outputBytes: UnsafeMutableRawBufferPointer) in
let status = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2),
passphrase,
passphrase.utf8.count,
salt,
salt.utf8.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1),
rounds,
outputBytes.baseAddress?.assumingMemoryBound(to: UInt8.self),
kCCKeySizeAES256)
guard status == kCCSuccess else {
throw Error.keyDerivationError
}
}
The code from the question would look like:
let value = data.withUnsafeBytes {
$0.baseAddress?.assumingMemoryBound(to: UInt32.self)
}
In cases where the 'withUnsafeBytes' is deprecated: use withUnsafeBytes<R>(…) warning persists, it seems like the compiler can get confused when the closure has only one line. Making the closure have two or more lines might remove the ambiguity.
One more way to fix this warning to use bindMemory(to:).
var rawKey = Data(count: rawKeyLength)
let status = rawKey.withUnsafeMutableBytes { rawBytes -> Int32 in
guard let rawBytes = rawBytes.bindMemory(to: UInt8.self).baseAddress else {
return Int32(kCCMemoryFailure)
}
return CCSymmetricKeyUnwrap(alg, ivBytes, iv.count, keyBytes, key.count, wrappedKeyBytes, wrappedKey.count, rawBytes, &rawKeyLength)
}
I got this error as I was trying to figure out a compression stream tutorial. To get it to work, I added a step of converting the raw buffer pointer to a UnsafePointer
Original code from a tutorial I was working on.
--> where input: Data
--> where stream: compression_stream
//Method that shows the deprecation alert
return input.withUnsafeBytes { (srcPointer: UnsafePointer<UInt8>) in
//holder
var output = Data()
//Source and destination buffers
stream.src_ptr = srcPointer //UnsafePointer<UInt8>
stream.src_size = input.count
… etc.
}
Code with a conversion to make the above code work with a valid method
return input.withUnsafeBytes { bufferPtr in
//holder
var output = Data()
//Get the Raw pointer at the initial position of the UnsafeRawBuffer
let base: UnsafeRawPointer? = bufferPtr.baseAddress
//Unwrap (Can be combined with above, but kept it separate for clarity)
guard let srcPointer = base else {
return output
}
//Bind the memory to the type
let count = bufferPtr.count
let typedPointer: UnsafePointer<UInt8> = srcPointer.bindMemory(to: UInt8.self, capacity: count)
// Jump back into the original method
stream.src_ptr = typedPointer //UnsafePointer<UInt8>
}

Cannot convert value of type 'NSRange'(aka'_NSRange') to expected argument type 'Range<Data.Index>' (aka 'Range<int>

there. I am a beginner in Swift and am trying to convert an older program to Swift3. I´ve managed to fix a bunch of errors, but I cannot get this function to work.
fileprivate func extractEntitlements(_ entitlementData: Data) -> NSDictionary? {
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: String.Encoding.ascii, allowLossyConversion: true)
let bytes = (originalEntitlementsData as NSData).bytes
for i in 0...(originalEntitlementsData.count - xmlStart!.count) {
if memcmp((xmlStart! as NSData).bytes, bytes + i, Int(xmlStart!.count)) == 0 {
let end = originalEntitlementsData.count - i
**originalEntitlementsData = originalEntitlementsData.subdata(in: NSMakeRange(i, end))**
break;
}
}
return NSString(data: originalEntitlementsData, encoding: String.Encoding.ascii.rawValue)?.propertyList() as? NSDictionary
}
Here is the error I get:
There are a bunch of questions regarding this error, but I am not being successful implementing a solution. Any tips on how I should proceed?
Thanks guys!
Ranges are more complicated and simpler at the same time in swift.
You want subdata(in: start..<end), which makes a Range<Int>, which is the type you need. However, in this case start and end refer to the beginning and end indexes of the range, not the location and length as you pass to NSMakeRange().
As #jrturton already said, subdata(in:) takes a Range<Int> argument,
so it should be
originalEntitlementsData = originalEntitlementsData.subdata(in: i..<i+end)
in your case. But note that all the conversions to NSData, taking
the .bytes, explicit loop and memcmp are not needed if you
take advantage of the existing range(of:) method of Data:
var originalEntitlementsData = entitlementData
let xmlStart = "<?xml".data(using: .utf8)!
if let range = originalEntitlementsData.range(of: xmlStart) {
originalEntitlementsData = originalEntitlementsData.subdata(in: range.lowerBound..<originalEntitlementsData.endIndex)
// Alternatively:
// originalEntitlementsData.removeSubrange(0..<range.lowerBound)
}

Getting key names for keyboard codes in Swift

I know others have asked similar questions, but I haven’t seen a definitive answer, and I’m still stuck. I’m trying to write a Swift function that takes a hardware-generated keyboard scan code, such as from an NSEvent, and returns the alpha-caps-locked name of the key, for the particular key arrangement (Dvorak, Qwerty, etc.) currently in effect in the OS (which might be different from the arrangement in effect when the code was generated).
It’s my understanding that the only way to do this is to invoke some very old Carbon functions, skirting a lot of the Swift’s extreme type-safety, something I don’t feel comfortable doing. Here is The Show So Far:
import Cocoa
import Carbon
func keyName (scanCode: UInt16) -> String?
{ let maxNameLength = 4, modifierKeys: UInt32 = 0x00000004 // Caps Lock (Carbon Era)
let deadKeys = UnsafeMutablePointer<UInt32>(bitPattern: 0x00000000),
nameBuffer = UnsafeMutablePointer<UniChar>.alloc(maxNameLength),
nameLength = UnsafeMutablePointer<Int>.alloc(1),
keyboardType = UInt32(LMGetKbdType())
let source = TISGetInputSourceProperty ( TISCopyCurrentKeyboardLayoutInputSource()
.takeRetainedValue(),
kTISPropertyUnicodeKeyLayoutData )
let dataRef = unsafeBitCast(source, CFDataRef.self)
let dataBuffer = CFDataGetBytePtr(dataRef)
let keyboardLayout = unsafeBitCast(dataBuffer, UnsafePointer <UCKeyboardLayout>.self)
let osStatus = UCKeyTranslate (keyboardLayout, scanCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
deadKeys, maxNameLength, nameLength, nameBuffer)
switch osStatus
{ case 0: return NSString (characters: nameBuffer, length: nameLength[0]) as String
default: NSLog (“Code: 0x%04X Status: %+i", scanCode, osStatus); return nil }
}
It doesn’t crash, which at this point I almost consider a game achievement in itself, but neither does it work. UCKeyTranslate always returns a status of -50, which I understand means there’s a parameter wrong. I suspect “keyboardLayout,” as it is the most complicated to set up. Can anyone see the parameter problem? Or is there a more up-to-date framework for this sort of thing?
As you already found out, you have to pass the address of a UInt32
variable as the deadKeyState argument. Allocating memory is one
way to solve that problem, but you must not forget to free the memory
eventually, otherwise the program will leak memory.
Another possible solution is to pass the address of a variable as
an inout-argument with &:
var deadKeys : UInt32 = 0
// ...
let osStatus = UCKeyTranslate(..., &deadKeys, ...)
This is a bit shorter and simpler, and you don't need to release the
memory. The same can be applied to nameBuffer and nameLength.
The unsafeBitCast() can be avoided by using the Unmanaged type,
compare Swift: CFArray : get values as UTF Strings for a similar problem and
more detailed explanations.
Also you can take advantage of the toll-free bridging between
CFData and NSData.
Then your function could look like this (Swift 2):
import Carbon
func keyName(virtualKeyCode: UInt16) -> String?
{
let maxNameLength = 4
var nameBuffer = [UniChar](count : maxNameLength, repeatedValue: 0)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys : UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)
let layoutData = Unmanaged<CFData>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue() as NSData
let keyboardLayout = UnsafePointer<UCKeyboardLayout>(layoutData.bytes)
let osStatus = UCKeyTranslate(keyboardLayout, virtualKeyCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", virtualKeyCode, osStatus);
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
Update for Swift 3:
import Carbon
func keyName(virtualKeyCode: UInt16) -> String? {
let maxNameLength = 4
var nameBuffer = [UniChar](repeating: 0, count : maxNameLength)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys: UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
NSLog("Could not get keyboard layout data")
return nil
}
let layoutData = Unmanaged<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data
let osStatus = layoutData.withUnsafeBytes {
UCKeyTranslate($0, virtualKeyCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
}
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", virtualKeyCode, osStatus);
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
Update for Swift 4:
As of Swift 4, Data.withUnsafeBytes calls the closure with a UnsafeRawBufferPointer which has to be bound a pointer to UCKeyboardLayout:
import Carbon
func keyName(virtualKeyCode: UInt16) -> String? {
let maxNameLength = 4
var nameBuffer = [UniChar](repeating: 0, count : maxNameLength)
var nameLength = 0
let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
var deadKeys: UInt32 = 0
let keyboardType = UInt32(LMGetKbdType())
let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
NSLog("Could not get keyboard layout data")
return nil
}
let layoutData = Unmanaged<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data
let osStatus = layoutData.withUnsafeBytes {
UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, virtualKeyCode, UInt16(kUCKeyActionDown),
modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
&deadKeys, maxNameLength, &nameLength, &nameBuffer)
}
guard osStatus == noErr else {
NSLog("Code: 0x%04X Status: %+i", virtualKeyCode, osStatus);
return nil
}
return String(utf16CodeUnits: nameBuffer, count: nameLength)
}
Ok, I believe I’ve found the problem. As odd as it feels to answer my own question, I understand that is the proper thing to do in cases like this.
The offending parameter seems to have been deadKeys. In the model code I was following, this was defined as a bit pattern. Although supposedly a pointer to something mutable, I’m not sure it really was that, because when I decided to redefine it to match the other two call-by-reference parameters of UCKeyTranslate, everything started working perfectly. The solution was to do an explicit .alloc, followed by an explicit zeroing of the referenced value. Here is my function updated:
func keyName ( scanCode: UInt16 ) -> String?
{ let maxNameLength = 4, modifierKeys: UInt32 = 0x00000004, // Caps Lock (Carbon Era Mask)
nameBuffer = UnsafeMutablePointer <UniChar> .alloc (maxNameLength),
nameLength = UnsafeMutablePointer <Int> .alloc (1),
deadKeys = UnsafeMutablePointer <UInt32> .alloc (1); deadKeys[0] = 0x00000000
let source = TISGetInputSourceProperty ( TISCopyCurrentKeyboardLayoutInputSource()
.takeRetainedValue(),
kTISPropertyUnicodeKeyLayoutData ),
keyboardLayout = unsafeBitCast ( CFDataGetBytePtr (unsafeBitCast (source, CFDataRef.self)),
UnsafePointer <UCKeyboardLayout>.self),
keyboardType = UInt32 (LMGetKbdType())
let osStatus = UCKeyTranslate (keyboardLayout, scanCode, UInt16 (kUCKeyActionDown),
modifierKeys, keyboardType, UInt32 (kUCKeyTranslateNoDeadKeysMask),
deadKeys, maxNameLength, nameLength, nameBuffer)
switch osStatus
{ case 0: return String.init (utf16CodeUnits: nameBuffer, count: nameLength[0])
default: NSLog ("Code: 0x%04X Status: %+i", scanCode, osStatus); return nil }
}
There are a few other changes, pretty much cosmetic: I eliminated a couple of intermediate constants leading up to the definition of keyboardLayout. (The “BitCasts” are only to satisfy Swiftian type-safety: they don’t really do anything else that I can see.) But the real problem was the original definition of deadKeys. I hope this will be of some use to somebody, at least until there is a non-Carbon alternative. (Will that happen?)

Swift structs to NSData and back

I have a struct containing a struct and an NSObject that I want to serialize into an NSData object:
struct Packet {
var name: String
var index: Int
var numberOfPackets: Int
var data: NSData
}
var thePacket = Packet(name: name, index: i, numberOfPackets: numberOfPackets, data: packetData)
How do I best serialize the Packet into an NSData, and how do I best deserialize it?
Using
var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))
of only gives me the pointers of name and data. I was exploring NSKeyedArchiver, but then I'd have to make Packet an object, and I'd prefer to keep it a struct.
Cheers
Nik
Not really getting any feedback, this is the solution I ended up with:
Make encode() and decode() functions for my struct
Change Int to Int64 so the Int has the same size on 32-bit and 64-bit platforms
Have an intermediate struct (ArchivedPacket) that has no String or Data, but only Int64
Here is my code, I would be very grateful for your feedback, especially if there are less cumbersome ways to do this:
public struct Packet {
var name: String
var index: Int64
var numberOfPackets: Int64
var data: NSData
struct ArchivedPacket {
var index : Int64
var numberOfPackets : Int64
var nameLength : Int64
var dataLength : Int64
}
func archive() -> NSData {
var archivedPacket = ArchivedPacket(index: Int64(self.index), numberOfPackets: Int64(self.numberOfPackets), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)), dataLength: Int64(self.data.length))
var metadata = NSData(
bytes: &archivedPacket,
length: sizeof(ArchivedPacket)
)
let archivedData = NSMutableData(data: metadata)
archivedData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
archivedData.appendData(data)
return archivedData
}
func unarchive(data: NSData!) -> Packet {
var archivedPacket = ArchivedPacket(index: 0, numberOfPackets: 0, nameLength: 0, dataLength: 0)
let archivedStructLength = sizeof(ArchivedPacket)
let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
archivedData.getBytes(&archivedPacket)
let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))
let nameData = data.subdataWithRange(nameRange)
let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
let theData = data.subdataWithRange(dataRange)
let packet = Packet(name: name, index: archivedPacket.index, numberOfPackets: archivedPacket.numberOfPackets, data: theData)
return packet
}
}
Swift 5
If you are on Apple platforms, use Codable now. See documentation.
Swift 3
This is an unaltered copy-paste from a Playground in Xcode 8.2.1 that works. It is a bit simpler than other answers.
import Foundation
enum WhizzoKind {
case floom
case bzzz
}
struct Whizzo {
let name: String
let num: Int
let kind:WhizzoKind
static func archive(w:Whizzo) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Whizzo>.stride)
}
static func unarchive(d:Data) -> Whizzo {
guard d.count == MemoryLayout<Whizzo>.stride else {
fatalError("BOOM!")
}
var w:Whizzo?
d.withUnsafeBytes({(bytes: UnsafePointer<Whizzo>)->Void in
w = UnsafePointer<Whizzo>(bytes).pointee
})
return w!
}
}
let thing = Whizzo(name:"Bob", num:77, kind:.bzzz)
print("thing = \(thing)")
let dataThing = Whizzo.archive(w: thing)
let convertedThing = Whizzo.unarchive(d: dataThing)
print("convertedThing = \(convertedThing)")
Notes
I couldn't make archive and unarchive instance methods because Data.init(bytes:​count:​) is mutating on the bytes parameter? And self isn't mutable, so... This made no sense to me.
The WhizzoKind enum is in there because that is something I care about. It's not important for the example. Someone might be paranoid about enums like I am.
I had to cobble this answer together from 4 other SO question/answers:
Getting data out of NSData with Swift
Extract struct from NSData in Swift
'bytes' is unavailable: use withUnsafeBytes instead
Unsafe bytes in Swift 3
And these docs:
- http://swiftdoc.org/v3.1/type/UnsafePointer/
And meditating on the Swift closure syntax until I wanted to scream.
So thanks to those other SO askers/authors.
Update
So this will not work across devices. For example, sending from iPhone 7 to Apple Watch. Because the stride is different. The above example is 80 bytes on iPhone 7 Simulator but 40 bytes on Apple Watch Series 2 Simulator.
It looks like the approach (but not syntax) by #niklassaers is still the only one that will work. I'm going to leave this answer here because it might help others with all the new Swift 3 syntax and API changes surrounding this topic.
Our only real hope is this Swift proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md
The easiest way for basic struct objects is PropertyListEncoder & PropertyListDecoder.
This is the sample code;
Swift 5
struct Packet: Codable {
var name: String
var index: Int
var numberOfPackets: Int
var data: Data
}
func getDataFromPacket(packet: Packet) -> Data?{
do{
let data = try PropertyListEncoder.init().encode(packet)
return data
}catch let error as NSError{
print(error.localizedDescription)
}
return nil
}
func getPacketFromData(data: Data) -> Packet?{
do{
let packet = try PropertyListDecoder.init().decode(Packet.self, from: data)
return packet
}catch let error as NSError{
print(error.localizedDescription)
}
return nil
}
I used Jeff's example to create the following struct:
struct Series {
var name: String?
var season: String?
var episode: String?
init(name: String?, season: String?, episode: String?) {
self.name = name
self.season = season
self.episode = episode
}
static func archive(w: Series) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Series>.stride)
}
static func unarchive(d: Data) -> Series {
guard d.count == MemoryLayout<Series>.stride else {
fatalError("Error!")
}
var w: Series?
d.withUnsafeBytes({(bytes: UnsafePointer<Series>) -> Void in
w = UnsafePointer<Series>(bytes).pointee
})
return w!
}
}
Like Dag mentioned the whole thing is a bit fragile. Sometimes the App crashes when the name contains whitespace or an underline/underscore, and sometimes it crashes just without reason. In all cases the name which is unarchived looks similar to this '4\200a\256'. Surprisingly this is not a problem in the case of season or episode (like in "Season 2"). Here the whitespace doesn't force the app to crash.
Maybe it's an alternative to encode the strings to utf8 but I'm not familiar enough with the archive/unarchive methods to adapt them for this case.
It seems this came out recently, and to me it is looking solid. Didn't try it yet...
https://github.com/a2/MessagePack.swift
Well, Swift doesn't have any magical serialization method, if that's what you are after. Since the good days of C, when you have a struct with a pointer, that is a flag that you can't serialize the bytes of that struct's instance without following the pointers and fetching their data. Same applies to Swift.
Depending on your Serialization needs and constraints, I'd say using NSCoding or even JSON strings will tidy your code and make it more predictable than the current state. Sure, you'll need to write a mapper, and there is an overhead. Everyone will tell you this: "Measure first".
Now, here is the interesting part:
If you really want to inline your data in that struct, and stream the contents without building the packet around NSData as you're doing, you can reserve bytes using Swift Tuples, which work much like how you would reserve bytes in C using char[CONST]:
struct what {
var x = 3
}
sizeof(what)
$R0: Int = 8
struct the {
var y = (3, 4, 5, 7, 8, 9, 33)
}
sizeof(the)
$R1: Int = 56
To expand a bit on this, I think it's pretty horrible, but possible. You can write to the tuple's memory location and read from it using something like this.