In Objective-C the code looked liked this and worked flawlessly,
NSInteger random = arc4random_uniform(99) + 1
NSData *data = [NSData dataWithBytes:& random length: sizeof(random)];
int value = *(int*)([data bytes]);
How can this be done 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
In Swift 4:
There are a couple of things to consider when extracting an integer value from a Data
stream. Signedness and Endianess. So I came up with a function in an extension to Data
that infers Signedness from the type of integer you want to extract and passes Endianess and Index
as parameters. The types of integers that can be extracted are all that conform to FixedWidthInteger
protocol.
Reminder: This function will not check if the Index
range is inside the bounds of the Data
buffer so it may crash depending on the size of the type being extracted in relation to the end of the buffer.
extension Data {
enum Endianness {
case BigEndian
case LittleEndian
}
func scanValue<T: FixedWidthInteger>(at index: Data.Index, endianess: Endianness) -> T {
let number: T = self.subdata(in: index..<index + MemoryLayout<T>.size).withUnsafeBytes({ $0.pointee })
switch endianess {
case .BigEndian:
return number.bigEndian
case .LittleEndian:
return number.littleEndian
}
}
}
Example:
let data = Data(bytes: [0xFF,0x1F,0x1F,0xFF])
let number1 = data.scanValue(at: 0, endianess: .LittleEndian) as UInt16
let number2 = data.scanValue(at: 0, endianess: .BigEndian) as UInt16
let number3: Int16 = data.scanValue(at: 2, endianess: .LittleEndian)
let number4: Int16 = data.scanValue(at: 2, endianess: .BigEndian)
Results:
number1 is 8191
number2 is 65311
number3 is -225
number4 is 8191
Observe the function calls to see how the type to be extracted is inferred. Of course Endianess doesn't make sense for Int8
or UInt8
, but the function works as expected.
Values can later be cast to Int
if needed.
For Swift 3, you can do this (little endian, but similar for big):
func getInt(fromData data: Data, start: Int) -> Int32 {
let intBits = data.withUnsafeBytes({(bytePointer: UnsafePointer<UInt8>) -> Int32 in
bytePointer.advanced(by: start).withMemoryRebound(to: Int32.self, capacity: 4) { pointer in
return pointer.pointee
}
})
return Int32(littleEndian: intBits)
}
You can modify this, add generics, etc. to fit other primitive types (and vary it depending on the endianness of the data bytes).
You might find this method useful if you are doing it a lot:
func readInteger<T : IntegerType>(data : NSData, start : Int) -> T {
var d : T = 0
data.getBytes(&d, range: NSRange(location: start, length: sizeof(T)))
return d
}
The function takes as a parameter the start position in the data to read the numeric type and it returns a value of the a type inferred from whatever you are assigning it to.
For example:
let i : UInt32 = readInteger(data, 10);
reads an 4 byte integer from position 10 in the data.
If you change UInt32
to UInt16
it will read two bytes.
Data to interger thanks @rghome
// MARK: - Extensions Data
extension Data {
/// To interger Data by range
///
/// - Parameters:
/// - data: Data
/// - startRange: StartRange
/// - endRange: EndRange
/// - Returns: Integer Typed
func toInterger<T : Integer>(withData data: NSData, withStartRange startRange: Int, withSizeRange endRange: Int) -> T {
var d : T = 0
(self as NSData).getBytes(&d, range: NSRange(location: startRange, length: endRange))
return d
}
}
101010
let value:Int = Data().toInterger(withStartRange: 0, withEndRange: 2)
get 10
get 2 Int
My contribution with a swift 3.1 extension :
extension NSData{
var int : Int{
var out: Int = 0
self.getBytes(&out, length: MemoryLayout<Int>.size)
return out
}
}
Just call .int to get your value, just like this :
let value = 50
let data = NSData(bytes: &value, length: 4)
print(data.int)