Apple 指望在 Swift 中指针可以尽可能减小登场概率,所以在 Swift 中指针被映射为了一个泛型类型,而且还比较抽象。这在必定程度上形成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操做经验的开发者 (包括我本身也是) 来讲,在 Swift 中使用指针确实是一个挑战。在这篇文章里,我但愿能从最基本的使用开始,总结一下在 Swift 中使用指针的一些常见方式和场景。这篇文章假定你至少知道指针是什么,若是对指针自己的概念不太清楚的话,能够先看看这篇 五分钟 C 指针教程 (或者它的 中文版本 ),应该会颇有帮助。linux
在 Swift 中,指针都使用一个特殊的类型来表示,那就是 UnsafePointer<T> 。遵循了 Cocoa 的一向不可变原则, UnsafePointer<T> 也是不可变的。固然对应地,它还有一个可变变体, UnsafeMutablePointer<T> 。绝大部分时间里,C 中的指针都会被以这两种类型引入到 Swift 中:C 中 const 修饰的指针对应 UnsafePointer (最多见的应该就是 C 字符串的 const char * 了),而其余可变的指针则对应 UnsafeMutablePointer 。除此以外,Swift 中存在表示一组连续数据指针的 UnsafeBufferPointer<T> ,表示非完整结构的不透明指针 COpaquePointer 等等。另外你可能已经注意到了,可以肯定指向内容的指针类型都是泛型的 struct,咱们能够经过这个泛型来对指针指向的类型进行约束以提供必定安全性。编程
对于一个 UnsafePointer<T> 类型,咱们能够经过 memory 属性对其进行取值,若是这个指针是可变的 UnsafeMutablePointer<T> 类型,咱们还能够经过 memory 对它进行赋值。好比咱们想要写一个利用指针直接操做内存的计数器的话,能够这么作:swift
func incrementor(ptr: UnsafeMutablePointer<Int>) { ptr.memory += 1 } var a = 10 incrementor(&a) a // 11
这里和 C 的指针使用相似,咱们经过在变量名前面加上 & 符号就能够将指向这个变量的指针传递到接受指针做为参数的方法中去。在上面的 incrementor 中咱们经过直接操做 memory 属性改变了指针指向的内容。数组
与这种作法相似的是使用 Swift 的 inout 关键字。咱们在将变量传入 inout 参数的函数时,一样也使用 & 符号表示地址。不过区别是在函数体内部咱们不须要处理指针类型,而是能够对参数直接进行操做。安全
func incrementor1(inout num: Int) { num += 1 } var b = 10 incrementor1(&b) b // 11
虽然 & 在参数传递时表示的意义和 C 中同样,是某个“变量的地址”,可是在 Swift 中咱们没有办法直接经过这个符号获取一个 UnsafePointer 的实例。须要注意这一点和 C 有所不一样:闭包
// 没法编译 let a = 100 let b = &a
在 Swift 中不能直接取到现有对象的地址,咱们仍是能够建立新的 UnsafeMutablePointer 对象。与 Swift 中其余对象的自动内存管理不一样,对于指针的管理,是须要咱们手动进行内存的申请和释放的。一个 UnsafeMutablePointer 的内存有三种可能状态:app
其中只有第三种状态下的指针是能够保证正常使用的。 UnsafeMutablePointer 的初始化方法 ( init ) 完成的都是从其余类型转换到 UnsafeMutablePointer 的工做。咱们若是想要新建一个指针,须要作的是使用 alloc: 这个类方法。该方法接受一个 num: Int 做为参数,将向系统申请 num 个数的对应泛型类型的内存。下面的代码申请了一个 Int 大小的内存,并返回指向这块内存的指针:ide
var intPtr = UnsafeMutablePointer<Int>.alloc(1) // "UnsafeMutablePointer(0x7FD3A8E00060)"
接下来应该作的是对这个指针的内容进行初始化,咱们可使用 initialize: 方法来完成初始化:函数
intPtr.initialize(10) // intPtr.memory 为 10
在完成初始化后,咱们就能够经过 memory 来操做指针指向的内存值了。性能
在使用以后,咱们最好尽快释放指针指向的内容和指针自己。与 initialize: 配对使用的 destroy 用来销毁指针指向的对象,而与 alloc: 对应的 dealloc: 用来释放以前申请的内存。它们都应该被配对使用:
intPtr.destroy() intPtr.dealloc(1) intPtr = nil
注意其实在这里对于 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 )。
对于传参,Swift 进行了简化,使用起来很是方便。可是若是咱们想要使用指针来像以前用 memory 的方式直接操做数组的话,就须要借助一个特殊的类型: UnsafeMutableBufferPointer 。Buffer Pointer 是一段连续的内存的指针,一般用来表达像是数组或者字典这样的集合类型。
var array = [1, 2, 3, 4, 5] var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count) // baseAddress 是第一个元素的指针 var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer<Int> basePtr.memory // 1 basePtr.memory = 10 basePtr.memory // 10 //下一个元素 var nextPtr = basePtr.successor() nextPtr.memory // 2
上面咱们说过,在 Swift 中不能像 C 里那样使用 & 符号直接获取地址来进行操做。若是咱们想对某个变量进行指针操做,咱们能够借助 withUnsafePointer 这个辅助方法。这个方法接受两个参数,第一个是 inout 的任意类型,第二个是一个闭包。Swift 会将第一个输入转换为指针,而后将这个转换后的 Unsafe 的指针做为参数,去调用闭包。使用起来大概是这个样子:
var test = 10 test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in ptr.memory += 1 return ptr.memory }) test // 11
这里其实咱们作了和文章一开始的 incrementor 相同的事情,区别在于不须要经过方法的调用来将值转换为指针。这么作的好处对于那些只会执行一次的指针操做来讲是显而易见的,能够将“咱们就是想对这个指针作点事儿”这个意图表达得更加清晰明确。
unsafeBitCast 是很是危险的操做,它会将一个指针指向的内存强制按位转换为目标的类型。由于这种转换是在 Swift 的类型管理以外进行的,所以编译器没法确保获得的类型是否确实正确,你必须明确地知道你在作什么。好比:
let arr = NSArray(object: "meow") let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), CFString.self) str // “meow”
由于 NSArray 是能够存听任意 NSObject 对象的,当咱们在使用 CFArrayGetValueAtIndex 从中取值的时候,获得的结果将是一个 UnsafePointer<Void> 。因为咱们很明白其中存放的是 String 对象,所以能够直接将其强制转换为 CFString 。
关于 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 指针操做和使用的知识会颇有帮助。
对于新的代码,尽可能避免使用 Unsafe 开头的类型,意味着能够避免不少没必要要的麻烦。Swift 给开发者带来的最大好处是可让咱们用更加先进的编程思想,进行更快和更专一的开发。只有在尊重这种思想的前提下,咱们才能更好地享受这门新语言带来的种种优点。显然,这种思想是不包括处处使用 UnsafePointer 的 :)