函數(shù)相關(guān)
可變形式參數(shù)
一個(gè)可變形式參數(shù)可以接受零或者多個(gè)特定類型的值虹统。當(dāng)調(diào)用函數(shù)的時(shí)候你可以利用可變形式參數(shù)來聲明形式參數(shù)可以被傳入值的數(shù)量是可變的弓坞∷砩酰可以通過在形式參數(shù)的類型名稱后邊插入三個(gè)點(diǎn)符號(hào)( ...)來書寫可變形式參數(shù)。
傳入到可變參數(shù)中的值在函數(shù)的主體中被當(dāng)作是對(duì)應(yīng)類型的數(shù)組渡冻。舉個(gè)栗子戚扳,一個(gè)可變參數(shù)的名字是 numbers類型是 Double...在函數(shù)的主體中它會(huì)被當(dāng)作名字是 numbers 類型是 [Double]的常量數(shù)組。
下面的栗子計(jì)算了一組任意長(zhǎng)度的數(shù)字的算術(shù)平均值(也叫做平均數(shù))族吻。
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
注意
一個(gè)函數(shù)最多只能有一個(gè)可變形式參數(shù)帽借。
輸入輸出形式參數(shù)
就像上面描述的,可變形式參數(shù)只能在函數(shù)的內(nèi)部做改變超歌。如果你想函數(shù)能夠修改一個(gè)形式參數(shù)的值砍艾,而且你想這些改變?cè)诤瘮?shù)結(jié)束之后依然生效,那么就需要將形式參數(shù)定義為輸入輸出形式參數(shù)巍举。
在形式參數(shù)定義開始的時(shí)候在前邊添加一個(gè) inout關(guān)鍵字可以定義一個(gè)輸入輸出形式參數(shù)脆荷。輸入輸出形式參數(shù)有一個(gè)能輸入給函數(shù)的值,函數(shù)能對(duì)其進(jìn)行修改懊悯,還能輸出到函數(shù)外邊替換原來的值蜓谋。
你只能把變量作為輸入輸出形式參數(shù)的實(shí)際參數(shù)。你不能用常量或者字面量作為實(shí)際參數(shù)炭分,因?yàn)槌A亢妥置媪坎荒苄薷墓屡臁T趯⒆兞孔鳛閷?shí)際參數(shù)傳遞給輸入輸出形式參數(shù)的時(shí)候,直接在它前邊添加一個(gè)和符號(hào) ( &) 來明確可以被函數(shù)修改欠窒。
注意
輸入輸出形式參數(shù)不能有默認(rèn)值覆旭,可變形式參數(shù)不能標(biāo)記為 inout,如果你給一個(gè)形式參數(shù)標(biāo)記了 inout岖妄,那么它們也不能標(biāo)記 var和 let了型将。
這里有一個(gè) swapTwoInts(::)函數(shù),它有兩個(gè)輸入輸出整數(shù)形式參數(shù) a和 b:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
函數(shù) swapTwoInts(::)只是簡(jiǎn)單的將 b和 a的值進(jìn)行了調(diào)換荐虐。函數(shù)將 a的值儲(chǔ)存在臨時(shí)常量 temporaryA中七兜,將 b的值賦給 a,然后再將 temporaryA的值賦給 b福扬。
你可以通過兩個(gè) Int類型的變量來調(diào)用函數(shù) swapTwoInts(::)去調(diào)換它們兩個(gè)的值腕铸,需要注意的是 someInt的值和 anotherInt的值在傳入函數(shù) swapTwoInts(::)時(shí)都添加了和符號(hào)。
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3"
上邊的栗子顯示了 someInt 和 anotherInt的原始值即使是在函數(shù)的外部定義的铛碑,也可被函數(shù) swapTwoInts(::)修改狠裹。
注意
輸入輸出形式參數(shù)與函數(shù)的返回值不同。上邊的 swapTwoInts沒有定義返回類型和返回值汽烦,但它仍然能修改 someInt和 anotherInt的值涛菠。輸入輸出形式參數(shù)是函數(shù)能影響到函數(shù)范圍外的另一種替代方式。
閉包相關(guān)
涉及關(guān)鍵字:尾隨閉包,逃逸閉包 @escaping俗冻,自動(dòng)閉包 @autoclosure 閉包是引用類型
https://www.cnswift.org/closures
枚舉相關(guān)
關(guān)聯(lián)值
有時(shí)將其它類型的關(guān)聯(lián)值與這些成員值一起存儲(chǔ)是很有用的礁叔。這樣你就可以將額外的自定義信息和成員值一起儲(chǔ)存,并且允許你在代碼中使用每次調(diào)用這個(gè)成員時(shí)都能使用它迄薄。
你可以定義 Swift 枚舉來存儲(chǔ)任意給定類型的關(guān)聯(lián)值琅关,如果需要的話不同枚舉成員關(guān)聯(lián)值的類型可以不同。
在Moya里面的使用場(chǎng)景
例如:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
這可以讀作:
“定義一個(gè)叫做 Barcode的枚舉類型讥蔽,它要么用 (Int, Int, Int, Int)類型的關(guān)聯(lián)值獲取 upc 值涣易,要么用 String 類型的關(guān)聯(lián)值獲取一個(gè) qrCode的值∏诶海”
遞歸枚舉
關(guān)鍵字:indirect
示例:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
類和結(jié)構(gòu)體
類與結(jié)構(gòu)體的對(duì)比
在 Swift 中類和結(jié)構(gòu)體有很多共同之處都毒,它們都能:
- 定義屬性用來存儲(chǔ)值;
- 定義方法用于提供功能碰缔;
- 定義下標(biāo)腳本用來允許使用下標(biāo)語法訪問值账劲;
- 定義初始化器用于初始化狀態(tài);
- 可以被擴(kuò)展來默認(rèn)所沒有的功能金抡;
- 遵循協(xié)議來針對(duì)特定類型提供標(biāo)準(zhǔn)功能瀑焦。
類有而結(jié)構(gòu)體沒有的額外功能:
- 繼承允許一個(gè)類繼承另一個(gè)類的特征;
- 類型轉(zhuǎn)換允許你在運(yùn)行檢查和解釋一個(gè)類實(shí)例的類型;
- 反初始化器允許一個(gè)類實(shí)例釋放任何其所被分配的資源梗肝;
- 引用計(jì)數(shù)允許不止一個(gè)對(duì)類實(shí)例的引用榛瓮。
注意
結(jié)構(gòu)體在你的代碼中通過復(fù)制來傳遞,并且并不會(huì)使用引用計(jì)數(shù)巫击。
結(jié)構(gòu)體和枚舉是值類型
值類型是一種當(dāng)它被指定到常量或者變量禀晓,或者被傳遞給函數(shù)時(shí)會(huì)被拷貝的類型。
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
println("cinema is now \(cinema.width) pixels wide")
//println "cinema is now 2048 pixels wide"
print("hd is still \(hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
類是引用類型
類和結(jié)構(gòu)體之間的選擇
按照通用準(zhǔn)則坝锰,當(dāng)符合以下一條或多條情形時(shí)應(yīng)考慮創(chuàng)建一個(gè)結(jié)構(gòu)體:
- 結(jié)構(gòu)體的主要目的是為了封裝一些相關(guān)的簡(jiǎn)單數(shù)據(jù)值粹懒;
- 當(dāng)你在賦予或者傳遞結(jié)構(gòu)實(shí)例時(shí),有理由需要封裝的數(shù)據(jù)值被拷貝而不是引用顷级;
- 任何存儲(chǔ)在結(jié)構(gòu)體中的屬性是值類型凫乖,也將被拷貝而不是被引用;
- 結(jié)構(gòu)體不需要從一個(gè)已存在類型繼承屬性或者行為弓颈。
合適的結(jié)構(gòu)體候選者包括:
- 幾何形狀的大小帽芽,可能封裝了一個(gè) width屬性和 height屬性,兩者都為 double類型翔冀;
- 一定范圍的路徑导街,可能封裝了一個(gè) start屬性和 length屬性,兩者為 Int類型橘蜜;
- 三維坐標(biāo)系的一個(gè)點(diǎn)菊匿,可能封裝了 x , y 和 z屬性付呕,他們都是 double類型计福。
屬性
注意
- 如果被標(biāo)記為 lazy 修飾符的屬性同時(shí)被多個(gè)線程訪問并且屬性還沒有被初始化跌捆,則無法保證屬性只初始化一次。
- 全局常量和變量永遠(yuǎn)是延遲計(jì)算的象颖,與延遲存儲(chǔ)屬性有著相同的行為佩厚。不同于延遲存儲(chǔ)屬性,全局常量和變量不需要標(biāo)記 lazy 修飾符说订。
方法
在實(shí)例方法中修改值類型
關(guān)鍵字:mutating
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// prints "The point is now at (3.0, 4.0)"
//你不能在常量結(jié)構(gòu)體類型里調(diào)用異變方法抄瓦,因?yàn)樽陨韺傩圆荒鼙桓淖儯退闼鼈兪亲兞繉傩裕?let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error
在異變方法里指定自身
異變方法可以指定整個(gè)實(shí)例給隱含的 self屬性陶冷。上文中那個(gè) Point的栗子可以用下邊的代碼代替:
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
初始化
類類型的初始化器委托
為了簡(jiǎn)化指定和便捷初始化器之間的調(diào)用關(guān)系钙姊,Swift 在初始化器之間的委托調(diào)用有下面的三個(gè)規(guī)則:
指定初始化器必須從它的直系父類調(diào)用指定初始化器。
便捷初始化器必須從相同的類里調(diào)用另一個(gè)初始化器埂伦。
便捷初始化器最終必須調(diào)用一個(gè)指定初始化器煞额。
簡(jiǎn)單記憶的這些規(guī)則的方法如下:
- 指定初始化器必須總是向上委托。
-
便捷初始化器必須總是橫向委托沾谜。
initializerDelegation01_2x.png
兩段式初始化
Swift 的類初始化是一個(gè)兩段式過程膊毁。在第一個(gè)階段,每一個(gè)存儲(chǔ)屬性被引入類為分配了一個(gè)初始值基跑。一旦每個(gè)存儲(chǔ)屬性的初始狀態(tài)被確定婚温,第二個(gè)階段就開始了,每個(gè)類都有機(jī)會(huì)在新的實(shí)例準(zhǔn)備使用之前來定制它的存儲(chǔ)屬性媳否。
兩段式初始化過程的使用讓初始化更加安全栅螟,同時(shí)在每個(gè)類的層級(jí)結(jié)構(gòu)給與了完備的靈活性。兩段式初始化過程可以防止屬性值在初始化之前被訪問篱竭,還可以防止屬性值被另一個(gè)初始化器意外地賦予不同的值力图。
注意
Swift 的兩段式初始化過程與 Objective-C 的初始化類似。主要的不同點(diǎn)是在第一階段室抽,Objective-C 為每一個(gè)屬性分配零或空值(例如 0 或 nil )搪哪。Swift 的初始化流程更加靈活,它允許你設(shè)置自定義的初始值坪圾,并可以自如應(yīng)對(duì) 0 或 nil 不為合法值的情況晓折。
Swift編譯器執(zhí)行四種有效的安全檢查來確保兩段式初始化過程能夠順利完成:
安全檢查 1
指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成兽泄。
如上所述漓概,一個(gè)對(duì)象的內(nèi)存只有在其所有儲(chǔ)存型屬性確定之后才能完全初始化。為了滿足這一規(guī)則病梢,指定初始化器必須保證它自己的屬性在它上交委托之前先完成初始化胃珍。
安全檢查 2
指定初始化器必須先向上委托父類初始化器梁肿,然后才能為繼承的屬性設(shè)置新值。如果不這樣做觅彰,指定初始化器賦予的新值將被父類中的初始化器所覆蓋吩蔑。
安全檢查 3
便捷初始化器必須先委托同類中的其它初始化器,然后再為任意屬性賦新值(包括同類里定義的屬性)填抬。如果沒這么做烛芬,便捷構(gòu)初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋。
安全檢查 4
初始化器在第一階段初始化完成之前飒责,不能調(diào)用任何實(shí)例方法赘娄、不能讀取任何實(shí)例屬性的值,也不能引用 self 作為值宏蛉。
直到第一階段結(jié)束類實(shí)例才完全合法遣臼。屬性只能被讀取,方法也只能被調(diào)用拾并,直到第一階段結(jié)束的時(shí)候揍堰,這個(gè)類實(shí)例才被看做是合法的。
以下是兩段初始化過程辟灰,基于上述四種檢查的流程:
階段 1
- 指定或便捷初始化器在類中被調(diào)用个榕;
- 為這個(gè)類的新實(shí)例分配內(nèi)存。內(nèi)存還沒有被初始化芥喇;
- 這個(gè)類的指定初始化器確保所有由此類引入的存儲(chǔ)屬性都有一個(gè)值∥鞑桑現(xiàn)在這些存儲(chǔ)屬性的內(nèi)存被初始化了;
- 指定初始化器上交父類的初始化器為其存儲(chǔ)屬性執(zhí)行相同的任務(wù)继控;
*這個(gè)調(diào)用父類初始化器的過程將沿著初始化器鏈一直向上進(jìn)行械馆,直到到達(dá)初始化器鏈的最頂部; - 一旦達(dá)了初始化器鏈的最頂部武通,在鏈頂部的類確保所有的存儲(chǔ)屬性都有一個(gè)值霹崎,此實(shí)例的內(nèi)存被認(rèn)為完全初始化了,此時(shí)第一階段完成冶忱。
階段 2
- 從頂部初始化器往下尾菇,鏈中的每一個(gè)指定初始化器都有機(jī)會(huì)進(jìn)一步定制實(shí)例。初始化器現(xiàn)在能夠訪問 self 并且可以修改它的屬性囚枪,調(diào)用它的實(shí)例方法等等派诬;
- 最終,鏈中任何便捷初始化器都有機(jī)會(huì)定制實(shí)例以及使用 slef 链沼。
自動(dòng)初始化器的繼承
子類默認(rèn)不會(huì)繼承父類初始化器默赂。總之括勺,在特定的情況下父類初始化器是可以被自動(dòng)繼承的缆八。實(shí)際上曲掰,這意味著在許多場(chǎng)景中你不必重寫父類初始化器,只要可以安全操作奈辰,你就可以毫不費(fèi)力地繼承父類的初始化器栏妖。
假設(shè)你為你子類引入的任何新的屬性都提供了默認(rèn)值,請(qǐng)遵守以下2個(gè)規(guī)則:
規(guī)則1
- 如果你的子類沒有定義任何指定初始化器冯挎,它會(huì)自動(dòng)繼承父類所有的指定初始化器底哥。
規(guī)則2
- 如果你的子類提供了所有父類指定初始化器的實(shí)現(xiàn)——要么是通過規(guī)則1繼承來的咙鞍,要么通過在定義中提供自定義實(shí)現(xiàn)的——那么它自動(dòng)繼承所有的父類便捷初始化器房官。
就算你的子類添加了更多的便捷初始化器,這些規(guī)則仍然適用续滋。