swift 5.0 函數(shù)及其底層實現(xiàn)

隨著swift語言的不斷發(fā)展怜跑,越來越來趨于穩(wěn)定化。現(xiàn)在也有很多公司使用swift來開發(fā)新的App,那么不會swift開發(fā)的iOS開發(fā)者在競爭中還是很弱勢的,所有學習swift是大勢所趨查排。本系列文章根據(jù)以往的學習積累和項目經(jīng)驗,從基礎到原理詳細說說swift的這點事兒抄沮,不喜勿噴跋核,交流指正請加微信。
WeChatdfdb8bfa7f0a84545d010ef18af70a98.png
一. 函數(shù)的定義
有返回值
    func text() -> Double {
        return 3.1415926
    }
    func sum(v1: Int,v2: Int) -> Int {
        return v1+v2;
    }
  // 調(diào)用
    sum(v1: 10, v2: 20)

形參默認是let合是,也只能是let

無返回值
    // 無返回值
    func sayHello() -> Void {
        print("Hello")
    }
返回元組:實現(xiàn)多返回值
 func calculate(v1:Int, v2: Int) -> (sum: Int, difference: Int, average: Int) {
    let sum = v1 + v2
    return (sum, v1 - v2, sum >> 1)
    }
    let result = calculate(v1: 20, v2: 10)
    result.sum // 30
    result.difference // 10
    result.average // 15
二. 參數(shù)標簽
修改參數(shù)標簽
    func goToWork(at time: String) {
        print("this time is \(time)")
    }
    goToWork(at: "08:00")
    // this time is 08:00
使用下劃線 _ 省略參數(shù)標簽
    func sum(_ v1:Int, _ v2: Int) -> Int {
        return v1 + v2
    }
    sum(10, 20)
三. 默認參數(shù)值
設置參數(shù)默認值
    func check(name: String = "nobody", age: Int, job: String = "none") {
        print("name=\(name), age=\(age), job=\(job)")
    }
    check(name: "Jack", age: 20, job: "Doctor") // name=Jack, age=20, job=Doctor
    check(name: "Rose", age: 18) // name=Rose, age=18, job=none
    check(age: 10, job: "Batman") // name=nobody, age=10, job=Batman
    check(age: 15) // name=nobody, age=15, job=none

注意: C++的默認參數(shù)值有個限制:必須從右往左設置了罪。由于Swift擁有參數(shù)標簽锭环,因此并沒有此類限制 n 但是在省略參數(shù)標簽時聪全,需要特別注意,避免出錯辅辩。

middle
    // 這里的middle不可以省略參數(shù)標簽
    func test(_ first: Int = 10, middle: Int, _ last: Int = 30) { }
    test(middle: 20)
四:可變參數(shù)
    func sum(_ numbers: Int...) -> Int {
        var total = 0
        for number in numbers {
            total += number
        }
        return total
    }
    sum(10, 20, 30, 40) // 100

一個函數(shù)最多只能有1個可變參數(shù)
緊跟在可變參數(shù)后面的參數(shù)不能省略參數(shù)標簽

    // 參數(shù)string不能省略標簽
    func test(_ numbers: Int..., string: String, _ other: String) { }
    test(10, 20, 30, string: "Jack", "Rose")
五. 輸入輸出參數(shù)
可以用inout定義一個輸入輸出參數(shù):可以在函數(shù)內(nèi)部修改外部實參的值
 var number = 10
 func test(_ num: Int) {
     num = 20
 }

 test(number)
原理分析:
swapValues(&num1, &num2) 函數(shù)調(diào)用傳遞的是地址傳遞還是值傳遞难礼,inout 這個函數(shù)內(nèi)部是怎么實現(xiàn)的呢娃圆?打上斷點,匯編代碼如下:
地址傳遞分析
點擊下一步進入test函數(shù)內(nèi)部
test函數(shù)內(nèi)部
總結: 輸入輸出函數(shù)底層其實就是地址傳遞蛾茉,將外部number的地址傳給函數(shù)讼呢,然后再給number進行賦值。
為了更好地驗證這個問題谦炬,將代碼做以下修改
var number = 10
 
 func test(_ num: Int) {
 
 }
 
 test(number)
然后將匯編代碼進行對比以下
var number = 10
 
 func test(_ num: Int) {
 
 }
 
 test(number)
 
 0x100000f5e <+78>: movq   -0x30(%rbp), %rdi
 0x100000f62 <+82>: callq  0x100000f70               ; TestSwift.test(Swift.Int) -> () at main.swift:24
 
 var number = 10
 
 func test(_ num: inout Int) {
 
 }
 
 test(&number)
 
 0x100000f47 <+55>: leaq   0x10ca(%rip), %rdi        ; TestSwift.number : Swift.Int
 0x100000f4e <+62>: callq  0x100000f70               ; TestSwift.test(inout Swift.Int) -> () at main.swift:24

可以看到正常的傳值函數(shù)都是movq悦屏,值傳遞。input 是leaq傳遞键思,地址傳遞础爬。movq就是通過取件碼找快遞,而leaq就是找到取件碼

注意點:

1. 可變參數(shù)不能標記為inout
2. inout參數(shù)不能有默認值
3. inout參數(shù)只能傳入可以被多次賦值的
4. inout參數(shù)的本質(zhì)是地址傳遞(引用傳遞)

六. 函數(shù)重載
規(guī)則
函數(shù)名相同
參數(shù)個數(shù)不同 || 參數(shù)類型不同 || 參數(shù)標簽不同
        // 例子
        func sum(v1: Int, v2: Int) -> Int {
            v1 + v2
        }
        
        func sum(v1: Int, v2: Int, v3: Int) -> Int {
            v1 + v2 + v3
        }// 參數(shù)個數(shù)不同
        
        func sum(v1: Int, v2: Double) -> Double {
            Double(v1) + v2
        } // 參數(shù)類型不同

        func sum(v1: Double, v2: Int) -> Double {
            v1 + Double(v2)
        } // 參數(shù)類型不同

        func sum(_ v1: Int, _ v2: Int) -> Int {
            v1 + v2
        } // 參數(shù)標簽不同

        func sum(a: Int, b: Int) -> Int {
            a + b
        } // 參數(shù)標簽不同
函數(shù)重載注意點
返回值類型與函數(shù)重載無關
函數(shù)重載注意點
默認參數(shù)值和函數(shù)重載一起使用產(chǎn)生二義性時吼鳞,編譯器并不會報錯(在C++中會報錯)
func sum(v1: Int, v2: Int) -> Int {
      v1 + v2
}
        
func sum(v1:Int, v2: Int, v3: Int = 10) -> Int {
     v1 + v2 + v3
}

// 會調(diào)用sum(v1: Int, v2: Int)
sum(v1: 10, v2: 20)
可變參數(shù)看蚜、省略參數(shù)標簽、函數(shù)重載一起使用產(chǎn)生二義性時赔桌,編譯器有可能會報錯
        func sum(v1: Int, v2: Int) -> Int {
            v1 + v2
        }
        
        func sum(_ v1: Int, _ v2: Int) -> Int {
            v1 + v2
        }
        
        func sum(_ numbers: Int...) -> Int {
            var total = 0
            for number in numbers {
                total += number
            }
            return total
        }
        // error: ambiguous use of 'sum'
        sum(10, 20)
七:內(nèi)聯(lián)函數(shù)
內(nèi)聯(lián)函數(shù)在C++這個函數(shù)里是有的供炎,那么在swift里面,怎么做的呢疾党?swift內(nèi)是不需要我們?nèi)ヂ暶鬟@個函數(shù)為內(nèi)聯(lián)函數(shù)的音诫。
如果開啟了編譯器優(yōu)化(Realease 模式默認會開啟優(yōu)化),編譯器會自動將某些函數(shù)變成內(nèi)聯(lián)函數(shù)仿贬。
我們打開項目纽竣。
選擇target---> Build Settings ---> 輸入optimization 如下圖:
image.png

搜索一下,我們會看到有一個Optimization Level 優(yōu)化級別茧泪,默認Debug情況下是NO Optimization(沒有優(yōu)化)蜓氨。Release(打包的時候)是Optimization for Speed[-D]是有優(yōu)化的。而且是speed是最快的队伟,按照速度最快的方式去優(yōu)化穴吹。如果我們開啟了優(yōu)化的話,它會自動將我們的某些函數(shù)變成內(nèi)聯(lián)函數(shù)嗜侮。也就是說港令,Debug模式下,不會將你的函數(shù)锈颗,變成內(nèi)聯(lián)函數(shù)顷霹。Release就變成內(nèi)聯(lián)函數(shù)。Release發(fā)布版會自動將某些函數(shù)變成內(nèi)聯(lián)函數(shù)击吱,也就意味這內(nèi)聯(lián)函數(shù)這種東西是有用的淋淀。肯定是可以優(yōu)化我們程序的系統(tǒng)的覆醇。

內(nèi)聯(lián)函數(shù)作用:
實現(xiàn)以下代碼:
func test() -> () {
    print("test")
}        
test()

按照我們正常的理解朵纷,當代碼調(diào)用test()這個函數(shù)時炭臭,系統(tǒng)會開辟棧空間袍辞,給這個函數(shù)鞋仍,在這個函數(shù)棧空間里面搅吁,去做它相應的事情威创。比如說分配局部變量,做相應的操作谎懦。

等這個函數(shù)執(zhí)行完之后呢那婉?就會將它的棧空間回收党瓮,所以這里牽扯一個椣昃妫空間的開辟跟回收的一個問題。所以寞奸,一旦調(diào)用函數(shù)就會出這個問題呛谜。

如果這段代碼能夠優(yōu)化成這樣 print("test")性能更好嗎?如下圖:

//        func test() -> () {
             print("test")
//         }
        
//        test()

因為你這個函數(shù)里面的代碼枪萄,特別的少隐岛。就是做一件什么事情,打印瓷翻。還不如把函數(shù)代碼抽出來聚凹,讓它直接打印呢?如下圖:
那么齐帚,這樣不是性能更高嗎妒牙?內(nèi)聯(lián)函數(shù)就是這個意思。內(nèi)聯(lián)函數(shù)會自動將函數(shù)調(diào)用展開成函數(shù)體代碼对妄。說白了湘今,是一個怎么樣的函數(shù)呢?如果你這個test是一個內(nèi)聯(lián)函數(shù)的話剪菱,它會之間將你的函數(shù)調(diào)用摩瞎,展開成函數(shù)體print("test")。這樣就是一種優(yōu)化孝常,這樣可以減少函數(shù)的調(diào)用開銷旗们,就不用開辟棧空間构灸,撤銷椛峡剩空間。

按照資料來說,Debug是沒有優(yōu)化的驰贷,Release是優(yōu)化的,用匯編看一下到底有沒有優(yōu)化

在test()帶一個斷點洛巢,cmd + R 運行

測試斷點

test函數(shù)調(diào)用轉(zhuǎn)成了匯編括袒,如下圖:

test函數(shù)匯編圖
我們發(fā)現(xiàn)test函數(shù)被調(diào)用了,所以再debug模式下并沒有沒內(nèi)聯(lián)稿茉。我們再將這個地方改成release 模式锹锰,運行一遍。
WX20200831-211506@2x.png
會發(fā)現(xiàn)一個奇怪的現(xiàn)象漓库,斷點沒進恃慧,但是結果已經(jīng)打印出來了。所以test()這段代碼并沒有調(diào)用渺蒿,可以打印出數(shù)據(jù)痢士,說明print("test")這行代碼肯定執(zhí)行了,那么我們把斷點打到print("test")位置茂装,如下圖:
WX20200831-211557@2x.png

匯編如圖所示:

WX20200831-212048@2x.png

發(fā)現(xiàn)最上邊TestSwift`main:怠蹂。main函數(shù)里面就有print函數(shù)
所以,看的出來少态,我們一旦開啟了編譯器的優(yōu)化城侧,它確實會將我們的函數(shù)進行內(nèi)聯(lián),直接將它函數(shù)體代碼彼妻,放到這個位置test()嫌佑。

并不是所有的函數(shù)都會被內(nèi)聯(lián),哪些函數(shù)不會被內(nèi)聯(lián)呢侨歉?
函數(shù)體比較長

就是如果函數(shù)內(nèi)部屋摇,寫了很多的時候,它發(fā)現(xiàn)代碼比較長幽邓,它就不會進行內(nèi)聯(lián)摊册,它就不會將你的函數(shù)體代碼放到調(diào)用的位置

func test() {
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")
    print("test1111")

}

如果這個函數(shù)調(diào)用次數(shù)比較多,假設如下圖test()調(diào)用的比較多颊艳,那么你要內(nèi)聯(lián)的話茅特,那不就相當于把函數(shù)里所有代碼,main里面放一份棋枕,原位置放一份白修。生成的匯編特別多,最終的機器也就是01重斑、01特別多兵睛,所以就會導致你代碼的體積就會變大,到時候你的安裝包也就會變大,所以這個也是比較智能的祖很。編譯器會自動去識別笛丙,它認為合適的就會進行內(nèi)聯(lián),不合適的它不會內(nèi)聯(lián)假颇,說白了胚鸯,上面代碼,就算你開啟了編譯器笨鸡,編譯器的優(yōu)化姜钳,它也會變成函數(shù)調(diào)用,不會給你做內(nèi)聯(lián)優(yōu)化形耗。

包含遞歸調(diào)用的函數(shù)也不會內(nèi)聯(lián)

如果你包含了遞歸調(diào)用哥桥,也不會內(nèi)聯(lián)。如下面代碼這樣寫:

func test() {
   test()
}

test()

像這種激涤,編譯器也不會內(nèi)聯(lián)拟糕,內(nèi)聯(lián)就是將函數(shù)調(diào)用展開成函數(shù)體代碼,然而函數(shù)體就這一句 test()倦踢,函數(shù)外邊test()已卸,展開后還是 test(),就是一個死循環(huán)硼一,所以編譯器也是很聰明的累澡,發(fā)現(xiàn)你有遞歸調(diào)用也不會給你內(nèi)聯(lián)。

包含動態(tài)派發(fā)

什么叫動態(tài)派發(fā)呢般贼?其實就是OC里面的動態(tài)綁定愧哟,如果包含了動態(tài)派發(fā)的函數(shù),它也不會進行內(nèi)聯(lián)哼蛆。

比如說蕊梧,我們有兩個類,一個Person類和Student類腮介,Student類繼承于Person肥矢。 Person中有一個test函數(shù)方法,子類Student叠洗,重寫一下父類的test方法甘改。如下圖:


2156697-4a7d1f9c804470c9.png

認真思考一個問題,舉個例子


2156697-9f9d6746a851a27c.png

上邊圖片,的兩句代碼灭抑,明顯是一個多態(tài)十艾。相當于OC里面的父類指針指向子類對象。那么你想一下test這個函數(shù)這個將來肯定要動態(tài)派發(fā)的腾节。所謂動態(tài)派發(fā)就是在運行時再決定調(diào)用誰的test忘嫉。

程序運行過程中荤牍,根據(jù)你的變量指向的對象來調(diào)用誰。再舉個例子庆冕,如果下面有個Teacher類

2156697-520c4d936f2d2b54.png

所以康吵,你思考一下,到時候如下圖访递,可能會變晦嵌。

2156697-0634c2e50a23bba9.png

就是說到時候,可能會指向Teacher,既然你這個變量力九,將來指向的對象是隨時可能會發(fā)生變化的。所以編譯器在編譯這個代碼的時候邑闺,沒辦法確定到底是調(diào)用Teacher類跌前、還是Student類中的test,所以這個叫做動態(tài)派發(fā)陡舅。沒有辦法進行內(nèi)聯(lián)抵乓。

想一想,內(nèi)聯(lián)的前提是什么靶衍?我已經(jīng)確定要調(diào)用某個灾炭,比如說我確定在編譯時期了你要調(diào)用某個類的test,那么就將函數(shù)體代碼放到這個位置如下圖:

2156697-421136b6ba015a79.png

這個颅眶,肯定不能內(nèi)聯(lián)蜈出。

關于swift的更多知識
請點擊 swift文集

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涛酗,隨后出現(xiàn)的幾起案子铡原,更是在濱河造成了極大的恐慌,老刑警劉巖商叹,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件燕刻,死亡現(xiàn)場離奇詭異,居然都是意外死亡剖笙,警方通過查閱死者的電腦和手機卵洗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弥咪,“玉大人过蹂,你說我怎么就攤上這事【壑粒” “怎么了榴啸?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晚岭。 經(jīng)常有香客問我鸥印,道長勋功,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任库说,我火速辦了婚禮狂鞋,結果婚禮上,老公的妹妹穿的比我還像新娘潜的。我一直安慰自己骚揍,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布啰挪。 她就那樣靜靜地躺著信不,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亡呵。 梳的紋絲不亂的頭發(fā)上抽活,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音锰什,去河邊找鬼下硕。 笑死,一個胖子當著我的面吹牛汁胆,可吹牛的內(nèi)容都是我干的梭姓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嫩码,長吁一口氣:“原來是場噩夢啊……” “哼誉尖!你這毒婦竟也來了?” 一聲冷哼從身側響起铸题,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤释牺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后回挽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體没咙,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年千劈,在試婚紗的時候發(fā)現(xiàn)自己被綠了祭刚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡墙牌,死狀恐怖涡驮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喜滨,我是刑警寧澤捉捅,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站虽风,受9級特大地震影響棒口,放射性物質(zhì)發(fā)生泄漏寄月。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一无牵、第九天 我趴在偏房一處隱蔽的房頂上張望漾肮。 院中可真熱鬧,春花似錦茎毁、人聲如沸克懊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谭溉。三九已至,卻和暖如春橡卤,著一層夾襖步出監(jiān)牢的瞬間扮念,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工蒜魄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扔亥,地道東北人场躯。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓谈为,卻偏偏與公主長得像,于是被迫代替她去往敵國和親踢关。 傳聞我的和親對象是個殘疾皇子伞鲫,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355