Getting data out of NSData with Swift
You may want to check out RawData which is really new and this guy just experimented a bit with this idea, so don't think that it's tested well or anything, some function aren't even implemented yet. It's basically a Swift-y wrapper around (you guessed it) raw data, a series of bytes.
Using this extension, you can initialise it with an NSData
instance:
extension RawData {
convenience init(data: NSData) {
self.init(UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length))
}
}
You'de be calling it like this:
let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!
let rawData = RawData(data: data)
EDIT: To answer your questions:
The thing is that data can be large, very large. You generally don't want to copy large stuff, as space is valuable. The difference between an array of [UInt8]
values and an NSData
instance is that the array gets copied every time, you give it to a function -> new copy, you do an assignment -> new copy. That's not very desirable with large data.
1) If you want the most native, safe way, without any 3rd party libraries as the one mentioned, you can do this:
let data = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length)
(I know it doesn't sound very safe, but believe me it is). You can use this almost like an ordinary array:
let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!
let bytes = UnsafeMutableBufferPointer(start: UnsafeMutablePointer<UInt8>(data.bytes), count: data.length)
for byte in bytes {}
bytes.indexOf(0)
bytes.maxElement()
and it doesn't copy the data as you pass it along.
2) UnsafeMutablePointer<Void>
is indeed very C-like, in this context it represents the starting value (also called base) in a sequence of pointers. The Void
type comes from C as well, it means that the pointer doesn't know what kind of value it's storing. You can cast all kinds of pointers to the type you're expecting like this: UnsafeMutablePointer<Int>(yourVoidPointer)
(This shouldn't crash). As mentioned before, you can use UnsafeMutableBufferPointer
to use it as a collection of your type. UnsafeMutableBufferPointer
is just a wrapper around your base pointer and the length (this explains the initialiser I used).
Your method of decoding the data directly into your struct does indeed work, the properties of a struct are in the right order, even after compile time, and the size of a struct is exactly the sum of it's stored properties. For simple data like yours that's totally fine. There is an alternative: To use the NSCoding
protocol. Advantage: safer. Disadvantage: You have to subclass NSObject. I think you should be sticking to the way you do it now. One thing I would change though is to put the decoding of your struct inside the struct itself and use sizeof
. Have it like this:
struct TreeDescription {
var id:UInt32 = 0x00000000
var channel:UInt8 = 0x00
var rssi:UInt8 = 0x00
init(data: NSData) {
data.getBytes(&self, length: sizeof(TreeDescription))
}
}
Another EDIT: You can always get the underlying data from an Unsafe(Mutable)Pointer<T>
with the method memory
whose return type is T
. If you need to you can always shift pointers (to get the next value e.g.) by just adding/subtracting Int
s to it.
EDIT answering your comment: You use the &
to pass an inout
variable, which can then be modified within the function. Because an inout
variable is basically the same as passing the pointer, the Swift devs decided to make it possible to pass &value
for an argument that is expecting an UnsafeMutablePointer
. Demonstration:
func inoutArray(inout array: [Int]) {}
func pointerArray(array: UnsafeMutablePointer<Int>) {}
var array = [1, 2, 3]
inoutArray(&array)
pointerArray(&array)
This also works for structs
(and maybe some other things)
Working with NSData in swift
If you want to modify the bytes retrieved from the NSData
object, then you should copy the bytes into a separate array
var bytes = [UInt8](count: msgData.length, repeatedValue: 0)
msgData.getBytes(&bytes, length: bytes.count)
bytes[11] = UInt8(UInt32(bytes[11]) | 0x0FF)
NSData
is an immutable object, and
let ptr = UnsafePointer<UInt8>(msgData.bytes)
is a constant pointer, so you must not modify the pointed-to data.
Alternatively, use a mutable data object from the beginning:
var msgData = NSMutableData(bytes: testBytes, length: testBytes.count)
let ptr = UnsafeMutablePointer<UInt8>(msgData.mutableBytes)
var bytes = UnsafeMutableBufferPointer<UInt8>(start: ptr, count: msgData.length)
bytes[11] = UInt8(UInt32(bytes[11]) | 0x0FF)
Note the usage of msgData.mutableBytes
instead of msgData.bytes
.
This will modify the data in msgData
directly.
Converting NSData to Integer in Swift
Like this:
var src: NSInteger = 2525
var out: NSInteger = 0
let data = NSData(bytes: &src, length: sizeof(NSInteger))
data.getBytes(&out, length: sizeof(NSInteger))
println(out) // ==> 2525
Converted NSData to Data in Swift, and now when I try to cast bytes to [UInt] I get a crash
The reason you are crashing is this expression:
bytes : UnsafePointer<[UInt]>
You are assuming that the data represents a series of UInt. So a pointer to the start of the data is not as unsafe pointer to a [UInt]
, an array of UInt; it is an unsafe pointer to a UInt, i.e. the first in the series. You should be saying:
bytes : UnsafePointer<UInt>
So much for the crash. Now let's talk about the thing you are mostly trying to do here.
I'm uncertain what the string format is supposed to do, but I do grasp that the idea of ntohl
is to guarantee the endianity of some C long ints (32 bits). So I'll omit the string format part and just talk about how you would take a stream of C long int received into a Data and reverse the endianity of the long ints.
Suppose d
is a mutable Data (i.e. declared with var
). Then, assuming it represents a sequence of UInt32 little-endian values and you want to convert those to big-endian, you would say:
let ct = d.count/4
d.withUnsafeMutableBytes{
(ptr:UnsafeMutablePointer<UInt32>) in
for ix in 0..<ct {
ptr[ix] = ptr[ix].bigEndian
}
}
NSData to String in Swift Issues
let testBytes : [UInt8] = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64]
func bytes2String(array:[UInt8]) -> String {
return String(data: NSData(bytes: array, length: array.count), encoding: NSUTF8StringEncoding) ?? ""
}
Xcode 8.2 • Swift 3.0.2
func bytes2String(_ array: [UInt8]) -> String {
return String(data: Data(bytes: array, count: array.count), encoding: .utf8) ?? ""
}
Testing:
bytes2String(testBytes) // "Hello World"
Swift. How to write bytes from NSData into another NSData?
You can extract the data using subdataWithRange()
:
let firstData1 = mutableData.subdataWithRange(NSMakeRange(sizeof(Int), length))
if let firstString1 = NSString(data: firstData1, encoding: NSUTF8StringEncoding) as? String {
println(firstString1)
} else {
// bad encoding
}
Your solution
var data = NSData()
mutableData.getBytes(&data, range: NSMakeRange(sizeof(Int), length))
does not work and crashes because NSData
is a reference type anddata
a pointer to the object. You are overwriting this pointer
and the following bytes in memory.
Related Topics
Insert a Comment Using the Youtube API and Alamofire
Ios: Ambiguous Use of Init(Cgimage)
Swift Cannot Infer Type from Context
Alamofire Returns Wrong Encoding
Swift Tdd & Async Urlsession - How to Test
How to Restore Window Position in an Osx Application
Does Swift Optimise Chained Creation and Copy of Structs
Swift Generics Protocols: Can Only Be Used as a Generic Constraint Problem
Swift: Convert String to Hex Color Code
How to Get Walking and Running Distance Using Healthkit in Swift
Playing Hls (M3U8) in Cocoa Os X Avplayer - Swift
Swift 2 Error Handling and While
How to Print Escape Sequence Characters in Swift
How to Cast Up to Super Class When There Is an Override Function in the Sub Class