Create an Array in Swift from an NSData Object

42,444

Solution 1

You can use the getBytes method of NSData:

// the number of elements:
let count = data.length / sizeof(UInt32)

// create array of appropriate length:
var array = [UInt32](count: count, repeatedValue: 0)

// copy bytes into array
data.getBytes(&array, length:count * sizeof(UInt32))

print(array)
// Output: [32, 4, 123, 4, 5, 2]

Update for Swift 3 (Xcode 8): Swift 3 has a new type struct Data which is a wrapper for NS(Mutable)Data with proper value semantics. The accessor methods are slightly different.

Array to Data:

var arr: [UInt32] = [32, 4, UInt32.max]
let data = Data(buffer: UnsafeBufferPointer(start: &arr, count: arr.count))
print(data) // <20000000 04000000 ffffffff>

Data to Array:

let arr2 = data.withUnsafeBytes {
    Array(UnsafeBufferPointer<UInt32>(start: $0, count: data.count/MemoryLayout<UInt32>.stride))
}
print(arr2) // [32, 4, 4294967295]

Update for Swift 5:

Array to Data:

let arr: [UInt32] = [32, 4, UInt32.max]
let data = Data(buffer: UnsafeBufferPointer(start: arr, count: arr.count))
print(data) // <20000000 04000000 ffffffff>

Data to Array:

var arr2 = Array<UInt32>(repeating: 0, count: data.count/MemoryLayout<UInt32>.stride)
_ = arr2.withUnsafeMutableBytes { data.copyBytes(to: $0) }
print(arr2) // [32, 4, 4294967295]

Solution 2

It's also possible to do this using an UnsafeBufferPointer, which is essentially an "array pointer", as it implements the Sequence protocol:

let data = NSData(/* ... */)

// Have to cast the pointer to the right size
let pointer = UnsafePointer<UInt32>(data.bytes)
let count = data.length / 4

// Get our buffer pointer and make an array out of it
let buffer = UnsafeBufferPointer<UInt32>(start:pointer, count:count)
let array = [UInt32](buffer)

This eliminates the need for initializing an empty array with duplicated elements first, to then overwrite it, although I have no idea if it's any faster. As it uses the Sequence protocol this implies iteration rather than fast memory copy, though I don't know if it's optimized when passed a buffer pointer. Then again, I'm not sure how fast the "create an empty array with X identical elements" initializer is either.

Solution 3

If you are dealing with Data to Array (I know for sure my array is going to be [String]), I am quite happy with this:

NSKeyedUnarchiver.unarchiveObject(with: yourData)

I hope it helps

Solution 4

Here is a generic way to do it.

import Foundation

extension Data {
    func elements <T> () -> [T] {
        return withUnsafeBytes {
            Array(UnsafeBufferPointer<T>(start: $0, count: count/MemoryLayout<T>.size))
        }
    }
}

let array = [1, 2, 3]
let data = Data(buffer: UnsafeBufferPointer(start: array, count: array.count))
let array2: [Int] = data.elements()

array == array2
// IN THE PLAYGROUND, THIS SHOWS AS TRUE

You must specify the type in the array2 line. Otherwise, the compiler cannot guess.

Share:
42,444
tassinari
Author by

tassinari

Updated on July 17, 2022

Comments

  • tassinari
    tassinari almost 2 years

    I'm trying to store an array of integers to disk in swift. I can get them into an NSData object to store, but getting them back out into an array is difficult. I can get a raw COpaquePointer to the data with data.bytes but can't find a way to initialize a new swift array with that pointer. Does anyone know how to do it?

    import Foundation
    
    var arr : UInt32[] = [32,4,123,4,5,2];
    
    let data = NSData(bytes: arr, length: arr.count * sizeof(UInt32))
    
    println(data)  //data looks good in the inspector
    
    // now get it back into an array?
    
  • Lucien
    Lucien almost 10 years
    Swift changed the way to write array declarations, in Xcode Beta 5 you get an error saying: Array types are now written with the brackets around the element type. So in the 2nd line of your code the correct is: var array = [UInt32](count: count, repeatedValue: 6)
  • Sifeng
    Sifeng over 9 years
    @Martin R: may i ask another similar question? // CGFloat to NSData var cgf: CGFloat = 5.123456 var f = Float(cgf) var nsf = NSNumber(float: f) var nsd = NSData(bytes: &nsf, length: sizeofValue(nsf)) The question is how can i convert nsdata back to CGFloat? Thanks in advance.
  • Martin R
    Martin R over 9 years
    @Sifeng: nsf is a pointer to an object, so you cannot use that to pack the number into data. Just var cgf: CGFloat = 5.123456 ; var data = NSData(bytes: &cgf, length: sizeofValue(cgf)) to pack and var cgf1: CGFloat = 0 ; data.getBytes(&cgf1, length: sizeofValue(cgf1)) to unpack.
  • voidref
    voidref about 9 years
    This seems to currently work better, as the getBytes method was causing a crash.
  • f3n1kc
    f3n1kc almost 9 years
    seems getBytes is much faster, compared to this one. For 1349890 bytes using an "array pointer" took 0.034 sec, while getBytes only 0.005 sec
  • Bobjt
    Bobjt about 8 years
    Wouldn't data length be divided by strideof(UInt32) since strideof measures the width of array elements e.g. type T in Array<T> ?
  • Martin R
    Martin R about 8 years
    @Bobjt: It would not make a difference for "simple" types like Int, but generally you are right!
  • Alix
    Alix over 7 years
    Getting following error : 'UnsafePointer<UInt8>' with an argument list of type '(UnsafeRawPointer)'. because data.bytes is UnsafeRawPointer. I am just trying to write equivalent code for const char* bytes = [data bytes];
  • Emmo213
    Emmo213 almost 7 years
    Running into the same issue as Alix.
  • algal
    algal over 6 years
    I think this answer is correct for UInt32 but incorrect in the general case. The call to UnsafeBufferPointer should calculate the count by doing data.count / MemoryLayout<Type>.stride. If the stride is greater than the size, then using size will calculate an incorrect count.
  • Martin R
    Martin R over 6 years
    @algal: size and stride are identical for all "simple" integer and floating point types (and for all types imported from C). But you are right, stride is the correct thing in the general case. Thanks for the feedback!
  • algal
    algal over 6 years
    @MartinR My pleasure. I believe you may have explained this matter to me originally, on an SO question not so long ago. :)
  • iSpain17
    iSpain17 over 4 years
    withUnsafeBufferPointer is now deprecated