使用Swift从NSData中获取数据

7
我发现Swift和NSData在一起非常令人沮丧。每次处理这个东西时,我感觉所有所谓的新发现的Swift安全性都荡然无存。无助的跟踪导致了很多崩溃。
所以,我学会了通过以下方法避免可怕的UnsafeMutablePointer:
var bytes = [UInt8](count: 15, repeatedValue: 0)
anNSData.getBytes(&bytes, length=15)

我还发现,我可以直接提取出奇异值:
var u32:UInt32 = 0
anNSData.getBytes(&u32, length=4)

这引出了两个中间问题:
1)有没有比硬编码常量更可靠的东西可以使用。如果这是C语言,我会使用sizeof,但我想我读到过也许我应该使用strideof而不是sizeof?而且这在[UInt8]上也行不通吧?
2)Swift文档说这个参数应该是_ buffer: UnsafeMutablePointer<Void>。那么这是怎么工作的呢?我只是幸运吗?为什么我要这样做,而不是更本地/受管理的[Uint8]结构?我想知道UnsafeMutablePointer是否是一种协议,但它是一个结构体。
通过直接读取值(而不是作为数组),我尝试了另一种类型的结构体。我有一个6字节的结构体,看起来像:
struct TreeDescription : Hashable {
    var id:UInt32 = 0x00000000
    var channel:UInt8 = 0x00
    var rssi:UInt8 = 0x00

    var hashValue:Int {
        return Int(self.id)
    }
}

实际上这个方法是有效的(之前我认为无效,但最终进行了清理操作,一些崩溃问题得到了解决)!

var tree = TreeDescription()
anNSData.getBytes(&newTree, length: 6)

但这让我担心结构体的内存对齐问题。为什么它能够工作?我在做这个时应该担心什么?
这让我感觉非常“C语言”。我认为Swift已经将C从Objective-C中剔除了。
1个回答

2
您可能想要查看名为RawData的新工具,作者只是在试验这个想法,因此不要认为它已经经过充分测试或其他方面的保证,有些功能甚至还没有实现。它基本上是一个Swift-y包装器,用于处理原始数据,即一系列字节。
使用此扩展程序,您可以使用NSData实例进行初始化:
extension RawData {
    convenience init(data: NSData) {
        self.init(UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length))
    }
}

您需要这样调用它:
let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!

let rawData = RawData(data: data)

编辑:回答你的问题:

事实上,数据可以非常大。一般来说,我们不希望复制大量数据,因为存储空间很宝贵。使用 [UInt8] 数组和 NSData 实例之间的区别在于,每次将数组传递给函数时都会进行复制,每次进行赋值时也会进行复制。对于大量数据来说,这并不理想。

1)如果你想要最本地化、最安全的方式,而又不需要任何第三方库(例如上述提到的那个),可以这样做:

let data = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length)

我知道听起来不太安全,但请相信我它是安全的。你可以像使用普通数组一样使用它:

let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!

let bytes = UnsafeMutableBufferPointer(start: UnsafeMutablePointer<UInt8>(data.bytes), count: data.length)

for byte in bytes {}
bytes.indexOf(0)
bytes.maxElement()

并且它不会在你传递数据时复制数据。

2)UnsafeMutablePointer<Void> 在这种情况下确实非常类似于 C,它表示指针序列中的起始值(也称为基值)。 Void 类型也来自 C,它表示指针不知道它存储的是什么类型的值。您可以将各种类型的指针强制转换为您期望的类型,如此进行:UnsafeMutablePointer<Int>(yourVoidPointer) (这不应该崩溃)。如前所述,您可以使用 UnsafeMutableBufferPointer 以其类型集合的形式使用它。 UnsafeMutableBufferPointer 只是您的基指针和长度的包装器(这解释了我使用的初始化程序)。

直接将数据解码到您的结构体的方法确实有效,结构体的属性在编译时之后仍按正确顺序排列,并且结构体的大小恰好等于其存储的属性的总和。对于像您的简单数据这样的数据,这完全可以。还有一种替代方式:使用 NSCoding 协议。优点:更安全。缺点:您必须子类化 NSObject。我认为您应该坚持现在的方式。但是我会改变一件事,将结构体的解码放在结构体本身中并使用 sizeof。将其设置为:

struct TreeDescription {
    var id:UInt32 = 0x00000000
    var channel:UInt8 = 0x00
    var rssi:UInt8 = 0x00

    init(data: NSData) {
        data.getBytes(&self, length: sizeof(TreeDescription))
    }
}

另外编辑:您可以使用方法memoryUnsafe(Mutable)Pointer<T>获取底层数据,其返回类型为T。如果需要,您可以始终通过将Int添加/减去到指针上来移动指针(例如获取下一个值)。

回答您的评论:您使用&来传递一个inout变量,它可以在函数内部修改。因为inout变量基本上与传递指针相同,所以Swift开发人员决定使其可以传递&value作为期望UnsafeMutablePointer的参数。演示:

func inoutArray(inout array: [Int]) {}

func pointerArray(array: UnsafeMutablePointer<Int>) {}

var array = [1, 2, 3]

inoutArray(&array)
pointerArray(&array)

这也适用于结构体(以及其他一些东西)。

我可能需要看一下。但这并不能真正帮助我理解为什么其他结构体(无论是数组还是非数组)似乎也能正常工作。 - Travis Griggs
@TravisGriggs 我更新了我的回答,请查看。 - Kametrixom
不错,我差不多到了,我想。为什么 [UInt8] 可以直接放在 UnsafeMutablePointer 的位置上还是不清楚?这是 inout 操作符(&)的本质吗?我注意到我可以使用与上述技术相反的方法来构造 NSData。例如:NSData(&myStruct, sizeof(myStruct)),它可以正常工作。我的天真的观察是,在变量前面加上 & 与 C 中的方式类似。 - Travis Griggs

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