隨著swift語言的不斷發(fā)展怜跑,越來越來趨于穩(wěn)定化。現(xiàn)在也有很多公司使用swift來開發(fā)新的App,那么不會swift開發(fā)的iOS開發(fā)者在競爭中還是很弱勢的,所有學習swift是大勢所趨查排。本系列文章根據(jù)以往的學習積累和項目經(jīng)驗,從基礎到原理詳細說說swift的這點事兒抄沮,不喜勿噴跋核,交流指正請加微信。
一. 函數(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)部
總結: 輸入輸出函數(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ù)重載一起使用產(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 如下圖:
搜索一下,我們會看到有一個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)成了匯編括袒,如下圖:
我們發(fā)現(xiàn)test函數(shù)被調(diào)用了,所以再debug模式下并沒有沒內(nèi)聯(lián)稿茉。我們再將這個地方改成release 模式锹锰,運行一遍。
會發(fā)現(xiàn)一個奇怪的現(xiàn)象漓库,斷點沒進恃慧,但是結果已經(jīng)打印出來了。所以test()這段代碼并沒有調(diào)用渺蒿,可以打印出數(shù)據(jù)痢士,說明print("test")這行代碼肯定執(zhí)行了,那么我們把斷點打到print("test")位置茂装,如下圖:
匯編如圖所示:
發(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方法甘改。如下圖:
認真思考一個問題,舉個例子
上邊圖片,的兩句代碼灭抑,明顯是一個多態(tài)十艾。相當于OC里面的父類指針指向子類對象。那么你想一下test這個函數(shù)這個將來肯定要動態(tài)派發(fā)的腾节。所謂動態(tài)派發(fā)就是在運行時再決定調(diào)用誰的test忘嫉。
程序運行過程中荤牍,根據(jù)你的變量指向的對象來調(diào)用誰。再舉個例子庆冕,如果下面有個Teacher類
所以康吵,你思考一下,到時候如下圖访递,可能會變晦嵌。
就是說到時候,可能會指向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ù)體代碼放到這個位置如下圖:
這個颅眶,肯定不能內(nèi)聯(lián)蜈出。
關于swift的更多知識
請點擊 swift文集