本文首發於我的博客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
參考資料:
更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。