8

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?

0

7 Answers 7

16

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
Sign up to request clarification or add additional context in comments.

4 Comments

Absolutely perfect! Thank you!
or sizeofValue(random)
In Swift3: sizeof(NSInteger) becomes MemoryLayout<NSInteger>.size and println becomes print.
Note that for getting the size directly from the type not the object in Swift 3 or later version of sizeofValue is MemoryLayout.size(ofValue: value)
10

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.

4 Comments

Hi - Have u got a much simpler solution for just converting data <02> to UInt8 2 or Int 2?
@richc: The simplest way to get a UInt8 is to simply use a subscript if you know exactly the index of the byte you want: data[index]
Data.Index inside a Data extension is redundant. Use just Index
I think you should be using MemoryLayout.stride - not size. See the docs for stride, which define it as "The number of bytes from the start of one instance of T to the start of the next when stored in contiguous memory or in an Array<T>" and "the number of bytes moved when an UnsafePointer<T> instance is incremented"
7

You can extend Data type, create a generic method, get the bytes and cast it or set the resulting type explicitly as needed:

extension Data {
    func object<T>(at index: Index = 0) -> T {
        subdata(in: index..<self.index(index, offsetBy: MemoryLayout<T>.size))
        .withUnsafeBytes { $0.load(as: T.self) }
    }
}

extension Numeric {
    var data: Data {
        var source = self
        return Data(bytes: &source, count: MemoryLayout<Self>.size)
    }
}

let data = Data([0xFF, 0x1F])   // 2 bytes

let uint16: UInt16 = data.object()      // 8191  littleEndian
let number1 = uint16.littleEndian       // 8191
let number2 = uint16.bigEndian          // 65311

let int16 = data.object() as Int16      // 8191   littleEndian
let number3 = int16.littleEndian        // 8191
let number4 = int16.bigEndian           // -225

print(number1) // 8191
print(number2) // 65311
print(number3) // 8191
print(number4) // -225

Testing with Int

let random = Int.random(in: 1...100)    // 15 UInt32
let data = random.data                  // 8 bytes  [15, 0, 0, 0, 0, 0, 0, 0]

Testing with UInt32

let random = UInt32.random(in: 1...100)  // 90 UInt32
let data = random.data                   // 4 bytes  [90, 0, 0, 0]

Testing with Double

let random = Double.random(in: 0...1)  // 0.2463145485351322 Double
let data = random.data                 // 8 bytes  [12, 99, 62, 49, 60, 135, 207, 63]

If you want to extract a subdata:

let data = Data([0xFF, 0x1F, 0x1F, 0xFF])      // 4 bytes

let uint16: UInt16 = data.object(at: 2)        //  65311 littleEndian
let number1 = uint16.littleEndian              // 65311
let number2 = uint16.bigEndian                 // 8191

let int16: Int16 = data.object(at: 2)          // -225   littleEndian
let number3 = int16.littleEndian               // -225
let number4 = int16.bigEndian                  // 8191

number1 // 65311
number2 // 8191
number3 // -225
number4 // 8191

Comments

6

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).

1 Comment

This is really the thing that irks me a bit about Swift. This is extremely complicated things for something that is technically extremely simple. I wonder if the compiler optimises all the bs away
5

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.

1 Comment

please elaborate some more on how to use it and what are the parameters to pass... thank you.
1

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)

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.