简体   繁体   中英

How to cast [Int8] to [UInt8] in Swift

I have a buffer that contains just characters

let buffer: [Int8] = ....

Then I need to pass this to a function process that takes [UInt8] as an argument.

func process(buffer: [UInt8]) {
    // some code
}

What would be the best way to pass the [Int8] buffer to cast to [Int8]? I know following code would work, but in this case the buffer contains just bunch of characters, and it is unnecessary to use functions like map.

process(buffer.map{ x in UInt8(x) }) // OK
process([UInt8](buffer)) // error
process(buffer as! [UInt8]) // error

I am using Xcode7 b3 Swift2.

IMO, the best way to do this would be to stick to the same base type throughout the whole application to avoid the whole need to do casts/coercions. That is, either use Int8 everywhere, or UInt8 , but not both.

If you have no choice, eg if you use two separate frameworks over which you have no control, and one of them uses Int8 while another uses UInt8 , then you should use map if you really want to use Swift. The latter 2 lines from your examples ( process([UInt8](buffer)) and process(buffer as! [UInt8]) ) look more like C approach to the problem, that is, we don't care that this area in memory is an array on singed integers we will now treat it as if it is unsigneds. Which basically throws whole Swift idea of strong types to the window.

What I would probably try to do is to use lazy sequences. Eg check if it possible to feed process() method with something like:

let convertedBuffer = lazy(buffer).map() {
    UInt8($0)
}

process(convertedBuffer)

This would at least save you from extra memory overhead (as otherwise you would have to keep 2 arrays), and possibly save you some performance (thanks to laziness).

I broadly agree with the other answers that you should just stick with map , however, if your array were truly huge, and it really was painful to create a whole second buffer just for converting to the same bit pattern, you could do it like this:

// first, change your process logic to be generic on any kind of container
func process<C: CollectionType where C.Generator.Element == UInt8>(chars: C) {
    // just to prove it's working...
    print(String(chars.map { UnicodeScalar($0) }))
}

// sample input
let a: [Int8] = [104, 101, 108, 108, 111]  // ascii "Hello"

// access the underlying raw buffer as a pointer
a.withUnsafeBufferPointer { buf -> Void in
    process(
        UnsafeBufferPointer(
            // cast the underlying pointer to the type you want
            start: UnsafePointer(buf.baseAddress), 
            count: buf.count))
}
// this prints [h, e, l, l, o]

Note withUnsafeBufferPointer means what it says. It's unsafe and you can corrupt memory if you get this wrong (be especially careful with the count). It works based on your external knowledge that, for example, if any of the integers are negative then your code doesn't mind them becoming corrupt unsigned integers. You might know that, but the Swift type system can't, so it won't allow it without resort to the unsafe types.

That said, the above code is correct and within the rules and these techniques are justifiable if you need the performance edge. You almost certainly won't unless you're dealing with gigantic amounts of data or writing a library that you will call a gazillion times.

It's also worth noting that there are circumstances where an array is not actually backed by a contiguous buffer (for example if it were cast from an NSArray ) in which case calling .withUnsafeBufferPointer will first copy all the elements into a contiguous array. Also, Swift arrays are growable so this copy of underlying elements happens often as the array grows. If performance is absolutely critical, you could consider allocating your own memory using UnsafeMutablePointer and using it fixed-size style using UnsafeBufferPointer .

For a humorous but definitely not within the rules example that you shouldn't actually use, this will also work:

process(unsafeBitCast(a, [UInt8].self))

It's also worth noting that these solutions are not the same as a.map { UInt8($0) } since the latter will trap at runtime if you pass it a negative integer. If this is a possibility you may need to filter them first.

You cannot cast arrays in Swift. It looks like you can, but what's really happening is that you are casting all the elements, one by one. Therefore, you can use cast notation with an array only if the elements can be cast.

Well, you cannot cast between numeric types in Swift. You have to coerce, which is a completely different thing - that is, you must make a new object of a different numeric type, based on the original object. The only way to use an Int8 where a UInt8 is expected is to coerce it: UInt8(x) .

So what is true for one Int8 is true for an entire array of Int8. You cannot cast from an array of Int8 to an array of UInt8, any more than you could cast one of them. The only way to end up with an array of UInt8 is to coerce all the elements. That is exactly what your map call does. That is the way to do it; saying it is "unnecessary" is meaningless.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM