1. 程式人生 > >Swift 3.0 從 ++ 的實現到 inout 和 defer 的小細節

Swift 3.0 從 ++ 的實現到 inout 和 defer 的小細節

本文是一個在 Swift 3.0 中自加和自減的實現
專案在我的「Playground」中開源

引言

Swift 3.0 中刪去了原 C Style 的自加和自減寫法,轉而推薦使用+=-=寫法。有時我們會在陣列下標處直接修改某些計數值,而+=寫法是個表示式,本身是不返回值的,因此無法代替原來的功能。

本文將通過過載運算子來找回自加和自減,順便提及一下inoutdefer的小知識。

正文

自加和自減分別包含了兩個運算子,共四個運算子。即:++前置/後置、–前置/後置。

前置運算子很簡單,因為是先自加,再返回自加後的值:

12345 @discardableResultprefix func++(x:inout Int)->Int{x+=1returnx}

測試時直接前置自加是可以實現功能的,這裡會打印出2,即陣列第二個元素:

123 var
i=0let list=[1,2,3,4]print(list[++i])

但後置運算子不同,是先使用本身的值,使用完以後再進行自加,因此它似乎應該長這樣:

123456 @discardableResult
postfix func++(x:inout Int)->Int{lety=xx+=1returny}

但事實上,可以使用defer關鍵字推遲加法運算,可以寫成這樣:

1234567 @discardableResultpostfix func++(x:inout Int)->Int{defer{x+=1}returnx}

defer程式碼塊會被壓入棧中,待函式結束時彈出棧執行。

程式碼看上去怪怪的,這樣真的沒問題嗎?deferreturn哪個會被先執行呢?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 中測試結果為:

11538901-7f0005d81542500d

對於最後一個比較複雜的表示式,可以看出 Swift 跟C語言一樣也是從右往左計算的,計算結果跟C語言中的是一致的:

12538901-9a60fae4cd1a121f

結語

其實 Swift 3.0 去除自加和自減也是有原因的,在非必要的情況下還是儘量使用推薦的寫法為好,這習慣確實在對程式碼可讀性沒什麼好處。養成良好編碼習慣從我做起,這段程式碼也僅供特殊情況下使用。