站在彙編角度深入瞭解 Swift(二)

小星星_ios發表於2020-04-03

if-else

  • c 中的 do-whileswift 中變成了 repeat-while
  • if 後面只能跟 bool 型別

for

  • 閉區間運算子: m...n
  • 開區間運算子: m..<n
  • 單側區間: 讓區間朝一個方向儘可能的遠: ...n
let range1: CloseRange<Int> = 1...3
let range2: Range<Int> = 1..<3
let range3: PartialRangeThrough<Int> = ...5
複製程式碼

switch

  • case、default 後面不能寫大括號
  • 預設不寫 break,並不會貫穿,如果想貫穿可以利用 falthrough(也可以把兩個條件寫在一起,用逗號隔開)
  • 必須保證能處理所有情況
  • case、default 後面至少要有一條語句
  • 如果不想做任何事,加個 break 即可
  • 支援更多的型別,比如 string、enum、int...
  • 區間匹配/元組匹配

where

這個在 swift 中很常用

函式的定義

func sum() -> Void { }
複製程式碼
  • 預設引數值
  • 可變引數
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
複製程式碼
  • 輸入輸出引數inout: 可以在函式內部修改外部實參的值
    • 本質其實就是地址傳遞,跟 c 語言裡面的一樣的
var num =10
func fix(_ num: inout Int) {
    num = 20
}
fix(&num)
複製程式碼

思考:為什麼利用元組可以不利用中間變數就進行交換?

func swap_(_ v1: inout Int, _ v2: inout Int) {
    (v1, v2) = (v2, v1)
}
複製程式碼

彙編分析

  1. 如何證明 inout 就是地址傳遞?
func test(a: inout Int) {
    a = 20
}
var a = 10
test(a: &a)
var b = a
複製程式碼

彙編

swiftstudy`main:
...
leaq   0x1109(%rip), %rax        ; swiftstudy.a : Swift.Int
movq   $0xa, 0x10fc(%rip)        ; _dyld_private + 4
movq   %rax, %rdi
leaq   0x10da(%rip), %rdi        ; swiftstudy.a : Swift.Int // 在這裡就可以看出來其實他傳的是地址,如果不是 inout 這個其實直接是 movq
callq  0x100000f60               ; swiftstudy.test(a: inout Swift.Int) -> () at main.swift:11
leaq   -0x18(%rbp), %rdi
...

swiftstudy`test(a:):
->  0x100000f60 <+0>:  pushq  %rbp
    0x100000f61 <+1>:  movq   %rsp, %rbp
    0x100000f64 <+4>:  movq   $0x0, -0x8(%rbp)
    0x100000f6c <+12>: movq   %rdi, -0x8(%rbp)
    0x100000f70 <+16>: movq   $0x14, (%rdi)
    0x100000f77 <+23>: popq   %rbp
    0x100000f78 <+24>: retq   
複製程式碼

函式過載

  • 規則
    • 函式名相同
    • 引數個數不同 || 引數型別不同 || 引數標籤不同
func sum(v1: Int, v2: Int) -> Int
func sum(v1: Int, v2: Int, v3: Int) -> Int
func sum(v1: Double, v2: Double) -> Double
複製程式碼
  • 注意點
    • 返回值型別與函式過載無關
    • 預設引數產生的二義性編譯器不會報錯
    • 可變引數、省略引數標籤、函式過載一起使用產生二義性時,編譯器有可能會報錯
sum 這個函式有二義性,就是有歧意
error: ambiguous use of 'sum'
複製程式碼

行內函數(Inline Function)

  • 如果開啟了編譯器優化(Release模式預設會開啟優化),編譯器會自動將某些函式變為行內函數
Optimization Level 設定了 Optimiz for Speed [-O]
複製程式碼
  • 呼叫函式都要做很多工作:呼叫前要先儲存暫存器,並在返回時恢復,複製實參,程式還必須轉向一個新位置執行
  • 其實就是將函式的呼叫展開成函式體,這樣就減少了呼叫函式的開銷
  • 哪些函式不會被自動內聯?
    • 理論上不要內聯超過十行的函式
    • 包含遞迴呼叫
    • 包含動態派發
  • swift 也提供了一些關鍵字來讓我們自己來控制是否內聯
    • @inline(never)
    • @inline(__always) 除了遞迴和動態派發的函式,就算函式體較長他也會內聯
  • 思考一下行內函數和巨集有什麼區別?
    • 巨集定義和行內函數的區別
    • 巨集基本沒啥優勢,所以 swift 裡面就不能定義巨集這種東西
    • 行內函數是程式碼被插入到呼叫者程式碼處的函式。如同 #define 巨集,行內函數通過避免被呼叫的開銷來提高執行效率,尤其是它能夠通過呼叫(“過程化整合”)被編譯器優化。 巨集定義不檢查函式引數,返回值什麼的,只是展開,相對來說,行內函數會檢查引數型別,所以更安全。行內函數和巨集很類似,而區別在於,巨集是由前處理器對巨集進行替代,而行內函數是通過編譯器控制來實現的。而且行內函數是真正的函式,只是在需要用到的時候,行內函數像巨集一樣的展開,所以取消了函式的引數壓棧,減少了呼叫的開銷。你可以象呼叫函式一樣來呼叫行內函數,而不必擔心會產生於處理巨集的一些問題。

函式型別(Function Type)

  • 可以把函式也看成是一種型別,這樣子就可以把函式當作引數或者返加值,或者是屬性 ...
    • 返回值是函式型別的函式,叫做高階函式
    • 比如 snapkit
一個函式有函式名、函式體、引數、返回值
func sum(_ a: Int, _ b: Int) -> Int { }
func p(f: (Int, Int) -> Int, a: Int, b: Int) { }
複製程式碼
  • typealias
    • 用來給型別起別名
    typealias Void ()
    複製程式碼

相關文章