I'm having trouble converting strings to and from UnsafeMutablePointers. The following code doesn't work, returning the wrong string.
// func rfcommChannelData(rfcommChannel: IOBluetoothRFCOMMChannel!, data dataPointer: UnsafeMutablePointer<Void>, length dataLength: Int)
func receivingData(data dataPointer: UnsafeMutablePointer<Void>, length dataLength: Int) {
let data = NSData(bytes: dataPointer, length: dataLength)
println("str = \(NSString(data: data, encoding: NSASCIIStringEncoding))")
}
// - (IOReturn)writeSync:(void *)data length:(UInt16)length;
func sendingData(data: UnsafeMutablePointer<Void>, length: UInt16) {
receivingData(data: data, length: Int(length))
}
var str: NSString = "Hello, playground"
var data = str.dataUsingEncoding(NSASCIIStringEncoding)!
var bytes = data.bytes
sendingData(&bytes, UInt16(data.length))
A link to the playground file is here. If anyone has experience using UnsafeMutablePointers in Swift for strings, I would very much appreciate some guidance as I have made no progress in the last few days. Thanks again!
With
var bytes = data.bytes
sendingData(&bytes, UInt16(data.length))
you pass the address of the variable bytes itself to the function, so what you see is the bytes that are used to represent that pointer.
What you probably want is
let str = "Hello, playground"
let data = str.dataUsingEncoding(NSASCIIStringEncoding)!
sendingData(UnsafeMutablePointer(data.bytes), UInt16(data.length))
to pass the pointer to the data bytes.
You should also consider
to use NSUTF8StringEncoding instead, because a conversion to
NSASCIIStringEncoding can fail.
Related
I am interfacing with libxml2 in swift, and the C APIs binding (still) produce UnsafePointer<Int8>! for c-strings. Whereas Swift APIs normally result in UnsafePointer<UInt8>!.
So my question is - am I doing the string to null-terminated C-string in a proper way?
let cfilePath = unsafeBitCast(myStringString.nulTerminatedUTF8.withUnsafeBufferPointer { $0.baseAddress }, to: UnsafePointer<Int8>.self)
Should I instead prefer using some other method instead of just bypassing Swift type checking with interpreting UInt8 bytes as Int8 bytes?
I'm not sure this solves your problem exactly but for a project where I am sending strings over bluetooth this did the trick:
extension String {
var nullTerminated: Data? {
if var data = self.data(using: String.Encoding.utf8) {
data.append(0)
return data
}
return nil
}
}
Use like this
let data = "asfasf".nullTerminated
I can't find the function the other answers are referencing: nulTerminatedUTF8. Maybe it already does this.
don't use unsafeBitCast for that!!
let cstr = "alpha".nulTerminatedUTF8
let int8arr = cstr.map{ Int8(bitPattern: $0) }
let uint8arr = Array(cstr)
print(int8arr.dynamicType, uint8arr.dynamicType)
// Array<Int8> Array<UInt8>
update
let uint8: UInt8 = 200
let int8 = Int8(bitPattern: uint8)
print(uint8, int8)
// 200 -56
I have decrypted using AES (CrytoSwift) and am left with an UInt8 array. What's the best approach to covert the UInt8 array into an appripriate string? Casting the array only gives back a string that looks exactly like the array. (When done in Java, a new READABLE string is obtained when casting Byte array to String).
I'm not sure if this is new to Swift 2, but at least the following works for me:
let chars: [UInt8] = [ 49, 50, 51 ]
var str = String(bytes: chars, encoding: NSUTF8StringEncoding)
In addition, if the array is formatted as a C string (trailing 0), these work:
str = String.fromCString(UnsafePointer(chars)) // UTF-8 is implicit
// or:
str = String(CString: UnsafePointer(chars), encoding: NSUTF8StringEncoding)
I don't know anything about CryptoSwift. But I can read the README:
For your convenience CryptoSwift provides two function to easily convert array of bytes to NSData and other way around:
let data = NSData.withBytes([0x01,0x02,0x03])
let bytes:[UInt8] = data.arrayOfBytes()
So my guess would be: call NSData.withBytes to get an NSData. Now you can presumably call NSString(data:encoding:) to get a string.
SWIFT 3.1
Try this:
let decData = NSData(bytes: enc, length: Int(enc.count))
let base64String = decData.base64EncodedString(options: .lineLength64Characters)
This is string output
Extensions allow you to easily modify the framework to fit your needs, essentially building your own version of Swift (my favorite part, I love to customize). Try this one out, put at the end of your view controller and call in viewDidLoad():
func stringToUInt8Extension() {
var cache : [UInt8] = []
for byte : UInt8 in 97..<97+26 {
cache.append(byte)
print(byte)
}
print("The letters of the alphabet are \(String(cache))")
}
extension String {
init(_ bytes: [UInt8]) {
self.init()
for b in bytes {
self.append(UnicodeScalar(b))
}
}
}
How do you convert a String to UInt8 array?
var str = "test"
var ar : [UInt8]
ar = str
Lots of different ways, depending on how you want to handle non-ASCII characters.
But the simplest code would be to use the utf8 view:
let string = "hello"
let array: [UInt8] = Array(string.utf8)
Note, this will result in multi-byte characters being represented as multiple entries in the array, i.e.:
let string = "é"
print(Array(string.utf8))
prints out [195, 169]
There’s also .nulTerminatedUTF8, which does the same thing, but then adds a nul-character to the end if your plan is to pass this somewhere as a C string (though if you’re doing that, you can probably also use .withCString or just use the implicit conversion for bridged C functions.
let str = "test"
let byteArray = [UInt8](str.utf8)
swift 4
func stringToUInt8Array(){
let str:String = "Swift 4"
let strToUInt8:[UInt8] = [UInt8](str.utf8)
print(strToUInt8)
}
I came to this question looking for how to convert to a Int8 array. This is how I'm doing it, but surely there's a less loopy way:
Method on an Extension for String
public func int8Array() -> [Int8] {
var retVal : [Int8] = []
for thing in self.utf16 {
retVal.append(Int8(thing))
}
return retVal
}
Note: storing a UTF-16 encoded character (2 bytes) in an Int8 (1 byte) will lead to information loss.
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.
I considered a lot of similar questions, but still can't get the compiler to accept this.
Socket Mobile API (in Objective-C) passes ISktScanDecodedData into a delegate method in Swift (the data may be binary, which I suppose is why it's not provided as string):
func onDecodedData(device: DeviceInfo?, DecodedData d: ISktScanDecodedData?) {
let symbology: String = d!.Name()
let rawData: UnsafePointer<UInt8> = d!.getData()
let rawDataSize: UInt32 = decoded!.getDataSize()
// want a String (UTF8 is OK) or Swifty byte array...
}
In C#, this code converts the raw data into a string:
string s = Marshal.PtrToStringAuto(d.GetData(), d.GetDataSize());
In Swift, I can get as far as UnsafeArray, but then I'm stuck:
let rawArray = UnsafeArray<UInt8>(start: rawData, length: Int(rawDataSize))
Alternatively I see String.fromCString and NSString.stringWithCharacters, but neither will accept the types of arguments at hand. If I could convert from UnsafePointer<UInt8> to UnsafePointer<()>, for example, then this would be available (though I'm not sure if it would even be safe):
NSData(bytesNoCopy: UnsafePointer<()>, length: Int, freeWhenDone: Bool)
Is there an obvious way to get a string out of all this?
This should work:
let data = NSData(bytes: rawData, length: Int(rawDataSize))
let str = String(data: data, encoding: NSUTF8StringEncoding)
Update for Swift 3:
let data = Data(bytes: rawData, count: Int(rawDataSize))
let str = String(data: data, encoding: String.Encoding.utf8)
The resulting string is nil if the data does not represent
a valid UTF-8 sequence.
How about this, 'pure' Swift 2.2 instead of using NSData:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == .None { // no length given, use \0 standard variant
return String.fromCString(cs)
}
let buflen = length + 1
var buf = UnsafeMutablePointer<CChar>.alloc(buflen)
memcpy(buf, cs, length))
buf[length] = 0 // zero terminate
let s = String.fromCString(buf)
buf.dealloc(buflen)
return s
}
}
and Swift 3:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == nil { // no length given, use \0 standard variant
return String(cString: cs)
}
let buflen = length + 1
let buf = UnsafeMutablePointer<CChar>.allocate(capacity: buflen)
memcpy(buf, cs, length)
buf[length] = 0 // zero terminate
let s = String(cString: buf)
buf.deallocate(capacity: buflen)
return s
}
}
Admittedly it's a bit stupid to alloc a buffer and copy the data just to add the zero terminator.
Obviously, as mentioned by Zaph, you need to make sure your assumptions about the string encoding are going to be right.