6

I'd like to use let rawDataFromArray = NSData(bytes: myArray, length: ???), but don't know how to get the bytes length for my array. Here are some examples of what could my array be:

let arr1 = [1, 2, 3]
let arr2 = [1.0, 23556789000.0]
let arr3 = ["hello", "ok", "👍"]

func arrayLength(myArray: Array) -> Int {
    var bytes = 0
    for object in myArray {
        // not sure what to do here
    }
    return bytes
}

I'm not sure if going through every element of the array (and in case of strings going through every character, since emojis could have more bytes representing them) is the proper way to do it.

How to get bytes size for array?
Could anyone tell me the proper way to do it?
Or maybe it's just that it is not good practice to convert Array to NSData in Swift?

I've also seen Converting Swift Array to NSData for persistent storage and Converting array of bytes to NSData and Custom Array to NSData, but couldn't figure out how to get bytes size for such arbitrary array.

4
  • 2
    See stackoverflow.com/questions/25714086/… for an example that can be used for an array of integers or floats. But you cannot simply treat an array of strings as NSData, because the String structure (which has a fixed size) contains opaque pointers to the actual character storage. Commented Nov 19, 2015 at 10:21
  • Thanks @MartinR. So it looks like a bad idea to convert an array of Strings to NSData. Can you explain a little more what are opaque pointers? Commented Nov 19, 2015 at 10:39
  • 1
    struct String has members which are not part of the "visible" API (but you can see them in the debugger). Some of these are pointers to the actual storage used for the string. (So for example, identical strings can share storage.) – The point is that struct String is not self-contained. If you pack that into NSData, transfer it somewhere else and unpack it, it will contain invalid pointers. – Of course you can create NSData from an array of strings, but have to append each string (e.g. as NUL-terminated UTF-8 string) separately. Commented Nov 19, 2015 at 10:45
  • Alternatively, use NSKeyedArchiver (as in the threads that you referenced). It depends on what you want to do with the data. Commented Nov 19, 2015 at 10:48

1 Answer 1

6

There seems to be a misunderstanding: For each type T, all instances of T have the same size which can be computed as sizeof(T). In the case of arrays, there can be a padding between array elements, therefore the total size needed for arr1 is

arr1.count * strideof(Int)

(Compare e.g. Swift: How to use sizeof? for the subtle differences between sizeof() and strideof()).

Therefore a generic function to create NSData from an array would be

extension Array {
    func asData() -> NSData {
        return self.withUnsafeBufferPointer({
            NSData(bytes: $0.baseAddress, length: count * strideof(Element))
        })
    }
}

Using withUnsafeBufferPointer() guarantees that the array uses contiguous storage for its elements.

In the case of "simple" types like Int and Float this gives the expected results:

let arr1 = [1, 2, 3]
print(arr1.asData())
// <01000000 00000000 02000000 00000000 03000000 00000000>

let arr2 = [1.0, 23556789000.0]
print(arr2.asData())
// <00000000 0000f03f 0000204c 60f01542>

However, it is useless for an array of strings:

let arr3 = ["hello", "ok", "👍"]
print(arr3.asData())
// <945b2900 01000000 05000000 00000000 00000000 00000000 9a5b2900 01000000 02000000 00000000 00000000 00000000 068d2900 01000000 02000000 00000080 00000000 00000000>

because struct String contains (hidden/undocumented) pointers to the actual character storage.

One possibility would be to append each string as a NUL-terminated UTF-8 string:

let data3 = NSMutableData()
arr3.forEach { string in
    string.withCString {
        data3.appendBytes($0, length: Int(strlen($0)) + 1)
    }
}
print(data3)
// <68656c6c 6f006f6b 00f09f91 8d00>

Alternatively, use NSKeyedArchiver as in the threads that you referenced.

Sign up to request clarification or add additional context in comments.

1 Comment

strideof(T) is MemoryLayout<T>.stride (as of... Swift 3?)

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.