本文首发于我的博客html
关于输入输出参数inout
在Swift之函数一文中,咱们已经有了初步的认识。如今咱们再继续深刻了解一下git
例如github
func swapValues(_ v1: inout Int, _ v2: inout Int) {
let tmp = v1
v1 = v2
v2 = tmp
//前面三行也能够换成 (v1, v2) = (v2, v1) 效果同样
}
var num1 = 10
var num2 = 20
swapValues(&num1, &num2)
print("num1 = \(num1), num2 = \(num2)")
输出: num1 = 20, num2 = 10
复制代码
注意点:编程
inout
是地址传递,对于不一样的状况具体怎么传递呢?汇编拨开云雾swift
以下代码,表示等边的多边形,其中width
表示边长,side
表示多边形边长数量 girth
表示周长,咱们知道 周长 = 边长 * 边数
bash
struct Shape {
// 宽、边长
var width: Int
// 边的数量
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
// 周长
var girth: Int {
set {
// 边长 = 周长 / 边数
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
// 周长 = 边长 * 边数
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
print("test");
num = 8
}
复制代码
以下代码app
struct Shape {
// 宽、边长
var width: Int
// 边的数量
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
// 周长
var girth: Int {
set {
// 边长 = 周长 / 边数
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
// 周长 = 边长 * 边数
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
print("test");
num = 8
}
var s = Shape(width: 10, side: 4)
test(&s.width) //这里打断点
s.show()
复制代码
打印结果为ide
test
getGirth
width=8, side=4, girth=32
复制代码
其中getGirth
这句打印是由于后面的show
方法须要获取girth
的值,若是咱们去掉最后一句,只有以下代码函数
var s = Shape(width: 10, side: 4)
test(&s.width)
复制代码
输出为post
test
复制代码
在上面代码中的 test(&s.width)
这一句打断点
关键代码为
//全局变量0x44be(%rip)的地址给寄存器rdi,rdi是全局变量s的地址
0x100000fbb <+107>: leaq 0x44be(%rip), %rdi ; testSwift.s : testSwift.Shape
// 调用test函数,其中rdi做为参数传入
0x100000fc2 <+114>: callq 0x100001930 ; testSwift.test(inout Swift.Int) -> () at main.swift:44
复制代码
把属性s.width
的地址值传递过去,进行修改
全局变量0x44be(%rip)的地址给寄存器rdi,rdi是全局变量s的地址
调用test函数,其中rdi做为参数传入
为何咱们代码中写的是 s.width ,但汇编传入的是s的地址呢?
为何rdi是做为参数呢?
首先分析一下,应该和前面存储属性不同的,由于若是直接修改存储属性side
的值,那怎么调动属性观察器的方法willSet
和didSet
呢?,
struct Shape {
// 宽、边长
var width: Int
// 边的数量
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
// 周长
var girth: Int {
set {
// 边长 = 周长 / 边数
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
// 周长 = 边长 * 边数
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
print("test");
num = 8
}
var s = Shape(width: 10, side: 4)
test(&s.side) //这里打断点
复制代码
输出
test
willSetSide 8
didSetSide 4 8
复制代码
关键汇编代码分析
//全局变量0x44e4(%rip)的地址给寄存器rdi,地址是testSwift.Shape + 8也就是size的地址
0x100000f9d <+109>: movq 0x44e4(%rip), %rax ; testSwift.s : testSwift.Shape + 8
//size的地址给局部变量-0x28(%rbp)
0x100000fa4 <+116>: movq %rax, -0x28(%rbp)
//局部变量-0x28(%rbp)的值给寄存器 %rdi
0x100000fa8 <+120>: leaq -0x28(%rbp), %rdi
//调用test函数,寄存器 %rdi做为参数传入
0x100000fac <+124>: callq 0x100001930 ; testSwift.test(inout Swift.Int) -> () at main.swift:44
//此时已经修改完了局部变量 -0x28(%rbp)对应的值 并把局部变量 -0x28(%rbp)的值传给rdi,
0x100000fb1 <+129>: movq -0x28(%rbp), %rdi
0x100000fb5 <+133>: leaq 0x44c4(%rip), %r13 ; testSwift.s : testSwift.Shape
//从截图中也能够看到此时%rdi里面是8,也就是 test函数中的 num = 8
0x100000fbc <+140>: callq 0x100001240 ; testSwift.Shape.side.setter : Swift.Int at <compiler-generated>
复制代码
testSwift.Shape.side.setter
函数中,调用side.willset
和 side.didset
对于带有属性观察器的存储属性size
size
的地址放在一个局部变量中test
方法,把局部变量的值修改setter
方法中,真正的修改计算属性size
girth
首先分析一下,应该和前面存储属性不同的,由于计算属性girth
没有本身的内存地址,
struct Shape {
// 宽、边长
var width: Int
// 边的数量
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
// 周长
var girth: Int {
set {
// 边长 = 周长 / 边数
width = newValue / side
print("setGirth", newValue)
}
get {
print("getGirth")
// 周长 = 边长 * 边数
return width * side
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
print("test");
num = 8
}
var s = Shape(width: 10, side: 4)
test(&s.girth) //这里打断点
复制代码
输出
getGirth
test
setGirth 8
复制代码
关键汇编代码分析
// 调用testSwift.Shape.girth.getter 方法,返回值放在rax中
0x100000fab <+123>: callq 0x100001580 ; testSwift.Shape.girth.getter : Swift.Int at main.swift:33
// 把getter的返回值放在 局部变量-0x28(%rbp)中
0x100000fb0 <+128>: movq %rax, -0x28(%rbp)
//局部变量-0x28(%rbp)的地址值 放在寄存器rdi
0x100000fb4 <+132>: leaq -0x28(%rbp), %rdi
//寄存器rdi的地址值传到testSwift.test函数中,进行修改
0x100000fb8 <+136>: callq 0x100001930 ; testSwift.test(inout Swift.Int) -> () at main.swift:44
// 局部变量-0x28(%rbp)的值,传到寄存器rdi中
0x100000fbd <+141>: movq -0x28(%rbp), %rdi
0x100000fc1 <+145>: leaq 0x44b8(%rip), %r13 ; testSwift.s : testSwift.Shape
// 寄存器rdi里面放的是局部变量-0x28(%rbp)的值 传入到Shape.girth.setter中
0x100000fc8 <+152>: callq 0x1000012f0 ; testSwift.Shape.girth.setter : Swift.Int at main.swift:28
复制代码
由于计算属性自己没有地址值,因此过程略显复杂
对于inout
修改计算属性girth
getter
方法,把返回值放在一个局部变量中test
方法,把局部变量的值修改setter
方法中,真正的修改计算属性girth
输入输出参数inout 本质就是引用传递,也就是地址传递,根据传过来的地址,修改对应的值。针对不一样的状况,其余处理不一样,
inout
带有属性观察器的存储属性size
size
的地址放在一个局部变量中test
方法,把局部变量的值修改setter
方法中,真正的修改计算属性size
inout
修改计算属性girth
getter
方法,把返回值放在一个局部变量中test
方法,把局部变量的值修改setter
方法中,真正的修改计算属性girth
参考资料:
更多资料,欢迎关注我的公众号,不定时分享各类技术文章。