Swift 3.0 從 ++ 的實現到 inout 和 defer 的小細節
本文是一個在 Swift 3.0 中自加和自減的實現
專案在我的「Playground」中開源
引言
Swift 3.0 中刪去了原 C Style 的自加和自減寫法,轉而推薦使用+=
和-=
寫法。有時我們會在陣列下標處直接修改某些計數值,而+=
寫法是個表示式,本身是不返回值的,因此無法代替原來的功能。
本文將通過過載運算子來找回自加和自減,順便提及一下inout
和defer
的小知識。
正文
自加和自減分別包含了兩個運算子,共四個運算子。即:++前置/後置、–前置/後置。
前置運算子很簡單,因為是先自加,再返回自加後的值:
12345 | @discardableResultprefix func++(x:inout Int)->Int{x+=1returnx} |
測試時直接前置自加是可以實現功能的,這裡會打印出2
,即陣列第二個元素:
123 | var |
但後置運算子不同,是先使用本身的值,使用完以後再進行自加,因此它似乎應該長這樣:
123456 | @discardableResult |
但事實上,可以使用defer
關鍵字推遲加法運算,可以寫成這樣:
1234567 | @discardableResultpostfix func++(x:inout Int)->Int{defer{x+=1}returnx} |
defer
程式碼塊會被壓入棧中,待函式結束時彈出棧執行。
程式碼看上去怪怪的,這樣真的沒問題嗎?defer
和return
哪個會被先執行呢?x 是inout
的,不論是哪個先被執行,結果返回的不都是應該返回已經 +1 的 x 嗎?
其實這裡包含了兩個問題,一是inout
究竟怎樣工作的,二是defer
究竟是怎樣工作的。
inout
inout
在寫法上與C語言傳遞地址的寫法十分類似,在呼叫函式傳入引數是帶有字首&
,就好像取地址傳進去了一樣,實則不然。
Swift 中 struct 是值型別的。
何為值,值是不能改變的,是immutable
的,任何對值的修改其實就是新構造了一個來替換原來的。這裡的inout
也是如此,並不是傳了地址進來,而是在這裡構造了一個新的結構體,當函式結束時會複製回去替換原來的結構體。
當然,這個複製並不一定會真的複製。Swift 的copy on write
也會分情況,當值型別的引用只有一個時是不會複製的,這段在貓神的書裡有提到。
defer
這裡defer
程式碼塊會被壓入棧中,函式結束時執行。到底啥時候執行呢?是在return
後執行,如果return
呼叫了其他函式,這個函式會在defer
程式碼塊執行之前被執行。這個簡單測試一下即可,也可以直接看彙編,上面程式碼的彙編長這樣:
12345678910111213 | SelfPlusDemo`++postfix(inout Int)->Int:0x100001c60:pushq%rbp0x100001c61:movq%rsp,%rbp0x100001c64:subq$0x10,%rsp0x100001c68:movq%rdi,-0x8(%rbp)0x100001c6c:movq(%rdi),%rax0x100001c6f:movq%rax,-0x10(%rbp)//在這裡呼叫了defer程式碼塊,首地址為0x100001c900x100001c73:callq0x100001c90;SelfPlusDemo.(++postfix(inout Swift.Int)->Swift.Int).($defer#1) () -> () at main.swift0x100001c78:movq-0x10(%rbp),%rax0x100001c7c:addq$0x10,%rsp0x100001c80:popq%rbp0x100001c81:retq |
回到上面自加的程式碼中。
這裡defer
程式碼塊會被壓入棧中,return 時還沒有被執行,因此這裡的值是正是我們想要的自加以前的值。按照inout
的原理,這個 x 指向的不是函式外面的 x,這只是個臨時變數,在defer
程式碼塊執時值被修改了也不會影響返回值,因為至始至終都是值,不是引用,在函式結束之後會把這個臨時變數拷貝回去,改變函式外的 x 值。
因此整個流程都是在操作和複製值,都是immutable
的,不存在互相影響的可能,因此這段程式碼能像想象中那樣工作。
按照這個思路完成自減程式碼即可。完整程式碼在我的「Playground」中開源。幾句簡單的程式碼基本能夠代替C語言的自加自減功能了。
Swift 中測試結果為:
對於最後一個比較複雜的表示式,可以看出 Swift 跟C語言一樣也是從右往左計算的,計算結果跟C語言中的是一致的:
結語
其實 Swift 3.0 去除自加和自減也是有原因的,在非必要的情況下還是儘量使用推薦的寫法為好,這習慣確實在對程式碼可讀性沒什麼好處。養成良好編碼習慣從我做起,這段程式碼也僅供特殊情況下使用。