本文系阅读阅读原章节后总结概括得出。由于需要我进行一定的概括提炼,如有不当之处欢迎读者斧正。如果你对内容有任何疑问,欢迎共同交流讨论。
在Swift中,初次接触inout
关键字以及它的用法,可能会让我们想起C/C++中的指针,但实际上Swift中inout
只不过是按值传递,然后再写回原变量,而不是按引用传递:
An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.
这样的好处在于它远比使用引用安全。首先举个最简单的例子看一看inout
关键字怎么用:
func inc(inout i: Int) {++i}var x = 0inc(&x)print(x) // 输出结果:“1”复制代码
参数x
传入到inc
函数中后,在函数内被修改为1,函数返回时这个值(1)覆盖了原来的x
的值(0),所以x
变成了1。
对比一下另一种同样能在函数内部改变变量值的实现方式——闭包:
func inc() -> () -> Int {var i = 0 //在inc函数内定义变量ireturn { ++i } // 闭包中截获变量i}let f = inc()print(f()) // 输出结果:“1”print(f()) // 输出结果:“2”复制代码
闭包是通过截获外部变量的引用从而实现对变量的修改的,我们通过闭包来证明,inout
参数是按值传递的:
func inc(inout i: Int) -> () -> Int {return { ++i } // 闭包中截获inout参数i}var x = 0let f = inc(&x)print(f()) // 输出结果:“1”print(x) // 输出结果:“0”复制代码
如果inout
参数是按引用传递,因为我们知道闭包会按引用截获变量,所以闭包内的++i
语句实际上会影响到我们定义的变量x
,因此最后一个输出的结果应该是1,但实际上运行结果是0。
这说明inout
参数是按值传递的,我们梳理一下整个过程:
- 首先变量
x
的值是0,它作为inout
参数传入inc
方法中,inc
方法内有一个x
的副本,闭包截获了这个副本的引用。 - 随后
inc
方法方法返回,此时的副本值还是0,所以外部的变量x
的值为0。 - 接下来我们调用闭包,副本值被改为1,但是外部的变量
x
的值不会受到任何影响,所以它依然为0。
如果在inc
方法中返回闭包之前就调用这个闭包,那么外部的变量x
的值就会被修改为1,这是因为在函数返回前,副本的值变成了1:
func inc(inout i: Int) -> () -> Int {let f = { ++i } // 闭包中截获inout参数if()return f}var x = 0let f = inc(&x)print(x) // 输出结果:“1”复制代码
&并不总表示inout
如果在函数声明中,参数是一个UnsafeMutablePointer
的指针,那么传递参数的时候也要加上&
,这和inout
参数看上去用法类似,但实际上这里是按引用传递而不是按值传递。我们可以改写一下之前的inc
方法:
func inc(i: UnsafeMutablePointer ) -> () -> Int {//函数内存储指针i的副本,闭包截获这个副本return {i.memory++return i.memory}}复制代码
这个方法的使用与之前类似。有兴趣的读者可以自行尝试。这里我们换一种调用方式,传入inc
方法的参数不是整数地址,而是数组的地址:
let f: () -> Intdo {var x = [0]f = inc(&x)}print(f())复制代码
为了与C兼容,Swift中的数组可以自动转成指针,但由于数组的地址不确定,所以这种写法其实会产生一种未定义行为(UB)。
需要注意的是,使用&
时,也许我们用的是Swift的inout
语义,这是安全的,也许用的是UnsafeMutablePointer
的指针,这需要我们格外小心。我们需要考虑它所指向的对象的生命周期。在后面的章节中会更加详细的讨论这一点。