本文首发于个人博客
前言
关于输入输出参数inout
在Swift之函数一文中,我们已经有了初步的认识。现在我们再继续深入了解一下
输入输出参数
- 说了形参只能是let,但是如果我们想再内部修改外部实参的值,可以用 inout 定义输入输出参数
例如
1 | func swapValues(_ v1: inout Int, _ v2: inout Int) { |
注意点:
- 可变参数不能标记为inout
- inout参数不能有默认值
- inout参数的本质是地址传递(引用传递)
- inout参数只能传入可以被多次赋值的
准备代码
inout
是地址传递,对于不同的情况具体怎么传递呢?汇编拨开云雾
如下代码,表示等边的多边形,其中width
表示边长,side
表示多边形边长数量 girth
表示周长,我们知道 周长 = 边长 * 边数
1 | struct Shape { |
inout 修改存储属性
先看打印结果
如下代码
1 | struct Shape { |
打印结果为
1 | test |
其中getGirth
这句打印是因为后面的show
方法需要获取girth
的值,如果我们去掉最后一句,只有如下代码
1 | var s = Shape(width: 10, side: 4) |
输出为
1 | test |
汇编分析
在上面代码中的 test(&s.width)
这一句打断点
关键代码为
1 | //全局变量0x44be(%rip)的地址给寄存器rdi,rdi是全局变量s的地址 |
小结
把属性s.width
的地址值传递过去,进行修改
- 全局变量0x44be(%rip)的地址给寄存器rdi,rdi是全局变量s的地址
- 调用test函数,其中rdi作为参数传入
为什么我们代码中写的是 s.width ,但汇编传入的是s的地址呢?
- 因为,width作为结构体的第一个属性变量,它的地址就是结构体s的地址
为什么rdi是作为参数呢?
- 汇编总结中我们知道 rdi、rsi、rdx、rcx、r8、r9等寄存器常用于存放函数参数。
inout 修改带有属性观察器的存储属性
分析
首先分析一下,应该和前面存储属性不一样的,因为如果直接修改存储属性side
的值,那怎么调动属性观察器的方法willSet
和didSet
呢?,
1 |
|
输出
1 | test |
查看汇编
关键汇编代码分析
1 | //全局变量0x44e4(%rip)的地址给寄存器rdi,地址是testSwift.Shape + 8也就是size的地址 |
testSwift.Shape.side.setter
函数中,调用side.willset
和 side.didset
小结
对于带有属性观察器的存储属性size
- 首先把
size
的地址放在一个局部变量中 - 然后调用
test
方法,把局部变量的值修改 - 再把局部变量传入到
setter
方法中,真正的修改计算属性size
inout 修改计算属性girth
分析
首先分析一下,应该和前面存储属性不一样的,因为计算属性girth
没有自己的内存地址,
1 |
|
输出
1 | getGirth |
查看汇编
汇编分析
关键汇编代码分析
1 |
|
小结
因为计算属性本身没有地址值,所以过程略显复杂
对于inout
修改计算属性girth
- 首先调用
getter
方法,把返回值放在一个局部变量中 - 然后调用
test
方法,把局部变量的值修改 - 再把局部变量传入到
setter
方法中,真正的修改计算属性girth
总结
针对本文代码的总结
输入输出参数inout 本质就是引用传递,也就是地址传递,根据传过来的地址,修改对应的值。针对不同的情况,其他处理不同,
- 普通存储属性,直接把地址值传过来修改就可以了。
- 对于
inout
带有属性观察器的存储属性size
- 首先把
size
的地址放在一个局部变量中 - 然后调用
test
方法,把局部变量的值修改 - 再把局部变量传入到
setter
方法中,真正的修改计算属性size
- 首先把
- 对于
inout
修改计算属性girth
- 首先调用
getter
方法,把返回值放在一个局部变量中 - 然后调用
test
方法,把局部变量的值修改 - 再把局部变量传入到
setter
方法中,真正的修改计算属性girth
- 首先调用
针对inout的总结
- 如果实参有物理内存地址,且没有设置属性观察器
- 直接将实参的内存地址传入函数(实参进行引用传递)
- 如果实参是计算属性 或者 设置了属性观察器
- 采取了Copy In Copy Out的做法
- 调用该函数时,先复制实参的值,产生副本【get】
- 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
- 函数返回后,再将副本的值覆盖实参的值【set】
- 总结:inout的本质就是引用传递(地址传递)
参考资料: