将带有长度的UnsafePointer转换为Swift数组类型

33
我希望能够找到最简单的方法,在Swift中实现合理的C互操作性。目前,我的难点是将一个UnsafePointer(它是一个const char *)转换为[Int8]数组。
目前,我有一个天真的算法,可以接受一个UnsafePointer和一定数量的字节,并逐个元素地将其转换为数组:
func convert(length: Int, data: UnsafePointer<Int8>) {

    let buffer = UnsafeBufferPointer(start: data, count: length);
    var arr: [Int8] = [Int8]()
    for (var i = 0; i < length; i++) {
        arr.append(buffer[i])
    }
}

使用arr.reserveCapacity(length)可以加快循环速度,但这并不能解决循环本身的问题。

我了解这个SO问题,它介绍了如何将UnsafePointer<Int8>转换为String,但是String[T]完全不同。在Swift中,是否有方便的方法可以将长度为length字节从UnsafePointer<T>中复制到[T]中?我更喜欢纯Swift方法,而不是通过NSData或类似方法进行处理。如果上述算法确实是唯一的方法,那么我愿意坚持使用它。

3个回答

51

您可以通过 UnsafeBufferPointer 初始化一个 Swift Array

func convert(length: Int, data: UnsafePointer<Int8>) -> [Int8] {

    let buffer = UnsafeBufferPointer(start: data, count: length);
    return Array(buffer)
}

这将创建一个所需大小的数组并复制数据。
或者作为通用函数:
func convert<T>(count: Int, data: UnsafePointer<T>) -> [T] {

    let buffer = UnsafeBufferPointer(start: data, count: count);
    return Array(buffer) 
}

这里的length指针所指向的数量。

如果您有一个UInt8指针,但想要从指向的数据创建一个[T]数组,那么这是一个可能的解决方案:

// Swift 2:
func convert<T>(length: Int, data: UnsafePointer<UInt8>, _: T.Type) -> [T] {

    let buffer = UnsafeBufferPointer<T>(start: UnsafePointer(data), count: length/strideof(T));
    return Array(buffer) 
}

// Swift 3:
func convert<T>(length: Int, data: UnsafePointer<UInt8>, _: T.Type) -> [T] {
    let numItems = length/MemoryLayout<T>.stride
    let buffer = data.withMemoryRebound(to: T.self, capacity: numItems) {
        UnsafeBufferPointer(start: $0, count: numItems)
    }
    return Array(buffer) 
}

其中length现在是字节的数量。例如:

let arr  = convert(12, data: ptr, Float.self)

该代码会从ptr所指向的12个字节中创建一个由3个Float组成的数组。


1
@AMomchilov:UnsafeBufferPointer只是一个指针,它不会分配内存,因此没有什么需要释放的。然后,Array由Swift管理。 - Martin R
如果已经分配了内存,则必须最终释放它。但是我们不知道指针来自哪里,它可能指向静态内存。在我看来,这与“convert”函数无关。 - Martin R
在 Swift 3 中,let buffer 行应该是 let buffer = data.withMemoryRebound(to: T.self, capacity: 1) {UnsafeBufferPointer(start: $0, count: length/MemoryLayout<T>.stride)} - kabiroberai
@kabiroberai:谢谢,已更新!(容量应该是目标项目的数量。) - Martin R
@MartinR 欢迎,不过我可以问一下为什么capacity需要是项目数量吗?即使将容量设置为1(因为$0只引用开始而不是整个数据),它似乎也能正常工作。 - kabiroberai
显示剩余3条评论

2
extension NSData {

    public func convertToBytes() -> [UInt8] {
        let count = self.length / sizeof(UInt8)
        var bytesArray = [UInt8](count: count, repeatedValue: 0)
        self.getBytes(&bytesArray, length:count * sizeof(UInt8))
        return bytesArray
    }
}

您可以将行数据转换为字节(Uint8)。

复制扩展并使用它...


这太有帮助了。我的 C <-> Swift 互操作性焦虑感正在消失。 - dugla
很高兴听到这个 ^^ - tBug

0

你也可以使用数组初始化函数:

init(unsafeUninitializedCapacity: Int, initializingWith initializer: (inout UnsafeMutableBufferPointer<Element>, inout Int) throws -> Void) rethrows

如果需要,首先将 unsafeRawPointer 转换为 unsafePointer 然后将指针转换为缓冲区指针,最后将缓冲区指针转换为数组。

例如,假设您有一个 unsafeRawPointer (dataPtr) 和它的大小 (dataSize)

let numberOfItems = dataSize / MemoryLayout<MyClass>.stride

let myArray = dataPtr.withMemoryRebound(to: MyClass.self,
                                        capacity: numberOfItems) { typedPtr in
    // Convert pointer to buffer pointer to access buffer via indices
    let bufferPointer = UnsafeBufferPointer(start: typedPtr, count: numberOfItems)
    // Construct array
    return [MyClass](unsafeUninitializedCapacity: numberOfItems) { arrayBuffer, count in
        count = numberOfItems
        for i in 0..<count {
            arrayBuffer[i] = bufferPointer[i]
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接