标题: Swift 中的指针使用 [打印本页] 作者: lingrixin 时间: 2015-1-22 00:17 标题: Swift 中的指针使用 Apple 期望在 Swift 中指针能够尽量减少登场几率,因此在 Swift 中指针被映射为了一个泛型类型,并且还比较抽象。这在一定程度上造成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者 (包括我自己也是) 来说,在 Swift 中使用指针确实是一个挑战。在这篇文章里,我希望能从最基本的使用开始,总结一下在 Swift 中使用指针的一些常见方式和场景。这篇文章假定你至少知道指针是什么,如果对指针本身的概念不太清楚的话,可以先看看这篇五分钟 C 指针教程 (或者它的中文版本),应该会很有帮助。
初步
在 Swift 中,指针都使用一个特殊的类型来表示,那就是 UnsafePointer<T>。遵循了 Cocoa 的一贯不可变原则,UnsafePointer<T> 也是不可变的。当然对应地,它还有一个可变变体,UnsafeMutablePointer<T>。绝大部分时间里,C 中的指针都会被以这两种类型引入到 Swift 中:C 中 const 修饰的指针对应 UnsafePointer (最常见的应该就是 C 字符串的 const char * 了),而其他可变的指针则对应 UnsafeMutablePointer。除此之外,Swift 中存在表示一组连续数据指针的 UnsafeBufferPointer<T>,表示非完整结构的不透明指针 COpaquePointer 等等。另外你可能已经注意到了,能够确定指向内容的指针类型都是泛型的 struct,我们可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。
注意其实在这里对于 Int 这样的在 C 中映射为 int 的 “平凡值” 来说,destroy 并不是必要的,因为这些值被分配在常量段上。但是对于像类的对象或者结构体实例来说,如果不保证初始化和摧毁配对的话,是会出现内存泄露的。所以没有特殊考虑的话,不论内存中到底是什么,保证 initialize: 和 destroy 配对会是一个好习惯。
指向数组的指针
在 Swift 中将一个数组作为参数传递到 C API 时,Swift 已经帮助我们完成了转换,这在 Apple 的官方博客中有个很好的例子:
import Accelerate
let a: [Float] = [1, 2, 3, 4]
let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
var result: [Float] = [0, 0, 0, 0]
vDSP_vadd(a, 1, b, 1, &result, 1, 4)
// result now contains [1.5, 2.25, 3.125, 4.0625]
复制代码
对于一般的接受 const 数组的 C API,其要求的类型为 UnsafePointer,而非 const 的数组则对应 UnsafeMutablePointer。使用时,对于 const 的参数,我们直接将 Swift 数组传入 (上例中的 a 和 b);而对于可变的数组,在前面加上 & 后传入即可 (上例中的 result)。
关于 unsafeBitCast 一种更常见的使用场景是不同类型的指针之间进行转换。因为指针本身所占用的的大小是一定的,所以指针的类型进行转换是不会出什么致命问题的。这在与一些 C API 协作时会很常见。比如有很多 C API 要求的输入是 void *,对应到 Swift 中为 UnsafePointer<Void>。我们可以通过下面这样的方式将任意指针转换为 UnsafePointer。
var count = 100
var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer<Int>) -> UnsafePointer<Void> in
return unsafeBitCast(a, UnsafePointer<Void>.self)
})
// voidPtr 是 UnsafePointer<Void>。相当于 C 中的 void *
// 转换回 UnsafePointer<Int>
var intPtr = unsafeBitCast(voidPtr, UnsafePointer<Int>.self)
intPtr.memory //100
复制代码
总结
Swift 从设计上来说就是以安全作为重要原则的,虽然可能有些啰嗦,但是还是要重申在 Swift 中直接使用和操作指针应该作为最后的手段,它们始终是无法确保安全的。从传统的 C 代码和与之无缝配合的 Objective-C 代码迁移到 Swift 并不是一件小工程,我们的代码库肯定会时不时出现一些和 C 协作的地方。我们当然可以选择使用 Swift 重写部分陈旧代码,但是对于像是安全或者性能至关重要的部分,我们可能除了继续使用 C API 以外别无选择。如果我们想要继续使用那些 API 的话,了解一些基本的 Swift 指针操作和使用的知识会很有帮助。