標簽(空格分隔): 未分類
基礎(相關概念)
1.元祖
元組(tuples)把多個值組合成一個復合值避消。元組內的值可以是任意類型,并不要求是相同類型
let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")
2.可選類型(optionals)
使用可選類型(optionals)來處理值可能缺失的情況锥腻“穑可選類型表示:
有值,等于 x居兆;或者 沒有值
C 和 Objective-C 中并沒有可選類型這個概念覆山。最接近的是 Objective-C 中的一個特性,一個方法要不返回一個對象要不返回nil泥栖,nil表示“缺少一個合法的對象”簇宽。然而,這只對對象起作用——對于結構體吧享,基本的 C 類型或者枚舉類型不起作用晦毙。對于這些類型,Objective-C 方法一般會返回一個特殊值(比如NSNotFound)來暗示值缺失耙蔑。這種方法假設方法的調用者知道并記得對特殊值進行判斷见妒。然而,Swift 的可選類型可以讓你暗示任意類型的值缺失,并不需要一個特殊值须揣。
//1.變量聲明
var surveyAnswer: String?
// surveyAnswer 被自動設置為 nil
//2.自動類型推斷
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測為類型 "Int?"盐股, 或者類型 "optional Int"
//3.可選類型賦值(兩種狀態(tài))
var serverResponseCode: Int? = 404
// serverResponseCode 包含一個可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值
if 語句以及強制解析
你可以使用 if 語句和 nil比較來判斷一個可選值是否包含值。你可以使用“相等”(==)或“不等”(!=)來執(zhí)行比較耻卡。如果可選類型有值疯汁,它將不等于 nil:
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
}
// 輸出 "convertedNumber contains some integer value."
當你確定可選類型確實包含值之后,你可以在可選的名字后面加一個感嘆號(!)來獲取值卵酪。這個驚嘆號表示“我知道這個可選有值幌蚊,請使用它±?ǎ”這被稱為可選值的強制解析(forced unwrapping):
if convertedNumber != nil {
print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 輸出 "convertedNumber has an integer value of 123."
可選綁定
使用可選綁定(optional binding)來判斷可選類型是否包含值溢豆,如果包含就把值賦給一個臨時常量或者變量∪诚郏可選綁定可以用在 if 和 while 語句中漩仙,這條語句不僅可以用來判斷可選類型中是否有值,同時可以將可選類型中的值賦給一個常量或者變量犹赖。
if let constantName = someOptional {
statements
}
3.錯誤處理
swift是類型安全的語言队他,對于錯誤中斷必須處理,而OC在這方面沒有如此嚴格的要求
//OC不帶error處理
[NSFileManager.defaultManager copyItemAtURL:[NSURL fileURLWithPath:@""] toURL:[NSURL fileURLWithPath:@""] error:nil];
//OC帶error處理(非強制)
NSError *error;
[NSFileManager.defaultManager copyItemAtURL:[NSURL fileURLWithPath:@""] toURL:[NSURL fileURLWithPath:@""] error:&error];
//swift
do{
try FileManager.default.copyItem(at: URL.init(fileURLWithPath: ""), to:URL.init(fileUrlWithPath:"")
}catch{
//error handle
}
集合類型
1.數(shù)組(Array)
創(chuàng)建數(shù)組
//1.創(chuàng)建空數(shù)組
var someInts = [Int]()
//2.創(chuàng)建帶有默認值或指定長度的數(shù)組
var threeDoubles = Array(repeating: 0.0, count: 3)
//3.創(chuàng)建一個含有數(shù)據(jù)的數(shù)組()
var shoppingList: [String] = ["Eggs", "Milk"]
var shoppingList = ["Eggs", "Milk"]
注:兩者等價峻村,自動類型推斷麸折,類型為[String]
訪問和修改數(shù)組
var shoppingList = [String][]
//1.拼接元素
shoppingList.append("egg")
//2.拼接數(shù)組
shoppingList = shoppingList + ["tomato","milk"]
//3.修改元素
shoppingList[0] = "orange juice"
shoppingList[0...2] = ["fish","noodle","pork","onion"]
注:可將其中的某幾項替換成某幾項(數(shù)量可不對等)
//4.插入元素
shoppingList.insert("Maple Syrup", at: 0)
//5.移除元素
shoppingList.remove(at:2)
shoppingList.removeLast()
shoppingList.removeFirst()
//6.清除所有元素
shoppingList = []
數(shù)組遍歷
for item in shoppingList {
print(item)
}
//帶索引的遍歷
for (index, value) in shoppingList. enumerated() {
print("Item \(String(index + 1)): \(value)")
}
2.集合(Set)
集合創(chuàng)建
var letters = Set<String>()
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] //自動推斷集合元素數(shù)據(jù)類型
集合賦值、遍歷
favoriteGenres.insert("Jazz")
favoriteGenres.remove("Jazz")
for genre in favoriteGenres {
print("\(genre)")
}
集合操作
- 使用intersection(_:)方法根據(jù)兩個集合中都包含的值創(chuàng)建的一個新的集合粘昨。
- 使用symmetricDifference(_:)方法根據(jù)在一個集合中但不在兩個集合中的值創(chuàng)建一個新的集合磕谅。
- 使用union(_:)方法根據(jù)兩個集合的值創(chuàng)建一個新的集合。
- 使用subtracting(_:)方法根據(jù)不在該集合中的值創(chuàng)建一個新的集合雾棺。
[圖片上傳失敗...(image-899197-1524110403362)]
let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits. intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits. symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]
3.字典(Dictionary)
創(chuàng)建字典
var namesOfIntegers = [Int: String]()
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
//類型自動推導為[String: String]
訪問和修改字典
airports["LHR"] = "London" //賦值
airports["APL"] = nil //移除鍵值對
airports.removeValue(forKey: "DUB")//移除鍵值對
airports.count //字典鍵值對數(shù)量
airports.isEmpty //字典是否為空
注:我們也可以使用下標語法來在字典中檢索特定鍵對應的值膊夹。因為有可能請求的鍵沒有對應的值存在,字典的下標訪問會返回對應值的類型的可選值捌浩。如果這個字典包含請求鍵所對應的值放刨,下標會返回一個包含這個存在值的可選值,否則將返回nil:
//做可選判斷是非常必要的
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport is not in the airports dictionary.")
}
字典遍歷
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
函數(shù)
1.函數(shù)的定義與調用
函數(shù)由 func + 函數(shù)名 + 函數(shù)參數(shù)(0-n個) + 返回參數(shù)(可為Void尸饺,返回參數(shù)可為元祖) 構成
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
指定參數(shù)標簽:你可以在參數(shù)名稱前指定它的參數(shù)標簽进统,中間以空格分隔:
func someFunction(argumentLabel parameterName: Int) {
// 在函數(shù)體內,parameterName 代表參數(shù)值,argumentLabele為參數(shù)標簽
}
忽略參數(shù)標簽:如果你不希望為某個參數(shù)添加一個標簽浪听,可以使用一個下劃線(_)來代替一個明確的參數(shù)標簽
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// 在函數(shù)體內螟碎,firstParameterName 和 secondParameterName 代表參數(shù)中的第一個和第二個參數(shù)值
}
someFunction(1, secondParameterName: 2)
默認參數(shù)值:你可以在函數(shù)體中通過給參數(shù)賦值來為任意一個參數(shù)定義默認值(Deafult Value)。當默認值被定義后迹栓,調用這個函數(shù)時可以忽略這個參數(shù)
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// 如果你在調用時候不傳第二個參數(shù)掉分,parameterWithDefault 會值為 12 傳入到函數(shù)體中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
可變參數(shù):一個可變參數(shù)(variadic parameter)可以接受零個或多個值。函數(shù)調用時酥郭,你可以用可變參數(shù)來指定函數(shù)參數(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)
// 返回 3.0, 是這 5 個數(shù)的平均數(shù)。
arithmeticMean(3, 8.25, 18.75)
// 返回 10.0, 是這 3 個數(shù)的平均數(shù)不从。
函數(shù)的使用
你可以定義一個類型為函數(shù)的常量或變量
var mathFunction: (Int, Int) -> Int = addTwoInts
函數(shù)類型作為參數(shù)類型
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
函數(shù)類型作為返回類型
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
閉包
閉包是自包含的函數(shù)代碼塊惜姐,可以在代碼中被傳遞和使用。Swift 中的閉包與 C 和 Objective-C 中的代碼塊(blocks)以及其他一些編程語言中的匿名函數(shù)比較相似椿息。
閉包可以捕獲和存儲其所在上下文中任意常量和變量的引用歹袁。被稱為包裹常量和變量。 Swift 會為你管理在捕獲過程中涉及到的所有內存操作寝优。
尾隨閉包
如果你需要將一個很長的閉包表達式作為最后一個參數(shù)傳遞給函數(shù)条舔,可以使用尾隨閉包來增強函數(shù)的可讀性。尾隨閉包是一個書寫在函數(shù)括號之后的閉包表達式倡勇,函數(shù)支持將其作為最后一個參數(shù)調用逞刷。在使用尾隨閉包時嘉涌,你不用寫出它的參數(shù)標簽:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數(shù)體部分
}
// 以下是不使用尾隨閉包進行函數(shù)調用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進行函數(shù)調用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
值捕獲
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經(jīng)不存在,閉包仍然可以在閉包函數(shù)體內引用和修改這些值踊沸。
Swift 中肄鸽,可以捕獲值的閉包的最簡單形式是嵌套函數(shù),也就是定義在其他函數(shù)的函數(shù)體內的函數(shù)警医。嵌套函數(shù)可以捕獲其外部函數(shù)所有的參數(shù)以及定義的常量和變量亿胸。
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
注:incrementer() 函數(shù)并沒有任何參數(shù),但是在函數(shù)體內訪問了 runningTotal 和 amount 變量预皇。這是因為它從外圍函數(shù)捕獲了 runningTotal 和 amount 變量的引用侈玄。捕獲引用保證了 runningTotal 和 amount 變量在調用完 makeIncrementer 后不會消失,并且保證了在下一次執(zhí)行 incrementer 函數(shù)時吟温,runningTotal 依舊存在序仙。
逃逸閉包
當一個閉包作為參數(shù)傳到一個函數(shù)中,但是這個閉包在函數(shù)返回之后才被執(zhí)行鲁豪,我們稱該閉包從函數(shù)中逃逸潘悼。當你定義接受閉包作為參數(shù)的函數(shù)時,你可以在參數(shù)名之前標注 @escaping爬橡,用來指明這個閉包是允許“逃逸”出這個函數(shù)的治唤。
一種能使閉包“逃逸”出函數(shù)的方法是,將這個閉包保存在一個函數(shù)外部定義的變量中糙申。舉個例子宾添,很多啟動異步操作的函數(shù)接受一個閉包參數(shù)作為 completion handler。這類函數(shù)會在異步操作開始之后立刻返回,但是閉包直到異步操作結束后才會被調用辞槐。在這種情況下掷漱,閉包需要“逃逸”出函數(shù),因為閉包需要在函數(shù)返回之后被調用榄檬。例如:
//逃逸閉包
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
//非逃逸閉包
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
將一個閉包標記為 @escaping 意味著你必須在閉包中顯式地引用 self卜范。比如說,在下面的代碼中鹿榜,傳遞到 someFunctionWithEscapingClosure(:) 中的閉包是一個逃逸閉包海雪,這意味著它需要顯式地引用 self。相對的舱殿,傳遞到 someFunctionWithNonescapingClosure(:) 中的閉包是一個非逃逸閉包奥裸,這意味著它可以隱式引用 self。
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"
completionHandlers.first?()
print(instance.x)
// 打印出 "100"
自動閉包
自動閉包是一種自動創(chuàng)建的閉包沪袭,用于包裝傳遞給函數(shù)作為參數(shù)的表達式湾宙。這種閉包不接受任何參數(shù),當它被調用的時候冈绊,會返回被包裝在其中的表達式的值侠鳄。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達式來代替顯式的閉包死宣。
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"
閉包作為函數(shù)參數(shù):
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
//顯式閉包傳參
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
//自動閉包傳參
serve(customer: customersInLine.remove(at: 0))
// 打印 "Now serving Ewa!"
枚舉
枚舉為一組相關的值定義了一個共同的類型伟恶,使你可以在你的代碼中以類型安全的方式來使用這些值。
enum CompassPoint {
case north
case south
case east
case west
}
enum CompassPoint {
case north,south,east,west
}
使用 Switch 語句匹配枚舉值
你可以使用switch語句匹配單個枚舉值:
directionToHead = CompassPoint.south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 打印 "Watch out for penguins”
遞歸枚舉
遞歸枚舉是一種枚舉類型毅该,它有一個或多個枚舉成員使用該枚舉類型的實例作為關聯(lián)值博秫。使用遞歸枚舉時,編譯器會插入一個間接層眶掌。你可以在枚舉成員前加上indirect來表示該成員可遞歸挡育。
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
//也可以在枚舉類型開頭加上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))
屬性
屬性將值跟特定的類、結構或枚舉關聯(lián)朴爬。存儲屬性存儲常量或變量作為實例的一部分即寒,而計算屬性計算(不是存儲)一個值。計算屬性可以用于類寝殴、結構體和枚舉蒿叠,存儲屬性只能用于類和結構體。
存儲屬性
簡單來說蚣常,一個存儲屬性就是存儲在特定類或結構體實例里的一個常量或變量市咽。存儲屬性可以是變量存儲屬性(用關鍵字 var 定義),也可以是常量存儲屬性(用關鍵字 let 定義)抵蚊。
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
延遲存儲屬性(OC的懶加載)
延遲存儲屬性是指當?shù)谝淮伪徽{用的時候才會計算其初始值的屬性施绎。在屬性聲明前使用 lazy 來標示一個延遲存儲屬性溯革。
注意:必須將延遲存儲屬性聲明成變量(使用 var 關鍵字),因為屬性的初始值可能在實例構造完成之后才會得到谷醉。而常量屬性在構造過程完成之前必須要有初始值致稀,因此無法聲明成延遲屬性。
延遲屬性很有用俱尼,當屬性的值依賴于在實例的構造過程結束后才會知道影響值的外部因素時抖单,或者當獲得屬性的初始值需要復雜或大量計算時,可以只在需要的時候計算它遇八。
class DataImporter {
/*
DataImporter 是一個負責將外部文件中的數(shù)據(jù)導入的類矛绘。
這個類的初始化會消耗不少時間。
*/
var fileName = "data.txt"
// 這里會提供數(shù)據(jù)導入功能
}
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// 這里會提供數(shù)據(jù)管理功能
}
計算屬性
除存儲屬性外刃永,類货矮、結構體和枚舉可以定義計算屬性。計算屬性不直接存儲值斯够,而是提供一個 getter 和一個可選的 setter囚玫,來間接獲取和設置其他屬性或變量的值。
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
只讀計算屬性
只有 getter 沒有 setter 的計算屬性就是只讀計算屬性读规。只讀計算屬性總是返回一個值抓督,可以通過點運算符訪問,但不能設置新的值掖桦。
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
var volume: Double {
return width * height * depth
}
}
屬性觀察器
屬性觀察器監(jiān)控和響應屬性值的變化本昏,每次屬性被設置值的時候都會調用屬性觀察器供汛,即使新值和當前值相同的時候也不例外枪汪。
可以為除了延遲存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重寫屬性的方式為繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器怔昨。你不必為非重寫的計算屬性添加屬性觀察器雀久,因為可以通過它的 setter 直接監(jiān)控和響應值的變化。
- willSet 在新的值被設置之前調用
- didSet 在新的值被設置之后立即調用
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
類型屬性
實例屬性屬于一個特定類型的實例趁舀,每創(chuàng)建一個實例赖捌,實例都擁有屬于自己的一套屬性值,實例之間的屬性相互獨立矮烹。
也可以為類型本身定義屬性越庇,無論創(chuàng)建了多少個該類型的實例,這些屬性都只有唯一一份奉狈。這種屬性就是類型屬性卤唉。
類型屬性用于定義某個類型所有實例共享的數(shù)據(jù),比如所有實例都能用的一個常量(就像 C 語言中的靜態(tài)常量)仁期,或者所有實例都能訪問的一個變量(就像 C 語言中的靜態(tài)變量)桑驱。
存儲型類型屬性可以是變量或常量竭恬,計算型類型屬性跟實例的計算型屬性一樣只能定義成變量屬性。
方法
實例方法 (Instance Methods)
實例方法是屬于某個特定類熬的、結構體或者枚舉類型實例的方法痊硕。實例方法提供訪問和修改實例屬性的方法或提供與實例目的相關的功能,并以此來支撐實例的功能押框。
class Counter {
var count = 0
func increment() {
count += 1
}
類型方法
實例方法是被某個類型的實例調用的方法岔绸。你也可以定義在類型本身上調用的方法,這種方法就叫做類型方法橡伞。在方法的func關鍵字之前加上關鍵字static亭螟,來指定類型方法。類還可以用關鍵字class來允許子類重寫父類的方法實現(xiàn)骑歹。
class SomeClass {
class func someTypeMethod() {
// 在這里實現(xiàn)類型方法
}
}
SomeClass.someTypeMethod()
繼承
一個類可以繼承另一個類的方法预烙,屬性和其它特性。當一個類繼承其它類時道媚,繼承類叫子類扁掸,被繼承類叫超類(或父類)。在 Swift 中最域,繼承是區(qū)分「類」與其它類型的一個基本特征谴分。
在 Swift 中,類可以調用和訪問超類的方法镀脂、屬性和下標牺蹄,并且可以重寫這些方法,屬性和下標來優(yōu)化或修改它們的行為薄翅。Swift 會檢查你的重寫定義在超類中是否有匹配的定義沙兰,以此確保你的重寫行為是正確的。
可以為類中繼承來的屬性添加屬性觀察器翘魄,這樣一來鼎天,當屬性值改變時,類就會被通知到暑竟≌洌可以為任何屬性添加屬性觀察器,無論它原本被定義為存儲型屬性還是計算型屬性但荤。
子類
class SomeClass: SomeSuperclass {
// 這里是子類的定義
}
class Bicycle: Vehicle {
var hasBasket = false
}
重寫
子類可以為繼承來的實例方法罗岖,類方法,實例屬性腹躁,或下標提供自己定制的實現(xiàn)桑包。我們把這種行為叫重寫。
如果要重寫某個特性潜慎,你需要在重寫定義的前面加上override關鍵字捡多。這么做蓖康,你就表明了你是想提供一個重寫版本,而非錯誤地提供了一個相同的定義垒手。意外的重寫行為可能會導致不可預知的錯誤蒜焊,任何缺少override關鍵字的重寫都會在編譯時被診斷為錯誤。
override關鍵字會提醒 Swift 編譯器去檢查該類的超類(或其中一個父類)是否有匹配重寫版本的聲明科贬。這個檢查可以確保你的重寫定義是正確的泳梆。
訪問超類的方法,屬性及下標
當你在子類中重寫超類的方法榜掌,屬性或下標時优妙,有時在你的重寫版本中使用已經(jīng)存在的超類實現(xiàn)會大有裨益。比如憎账,你可以完善已有實現(xiàn)的行為套硼,或在一個繼承來的變量中存儲一個修改過的值。
在合適的地方胞皱,你可以通過使用super前綴來訪問超類版本的方法邪意,屬性或下標:
- 在方法someMethod()的重寫實現(xiàn)中,可以通過super.someMethod()來調用超類版本的someMethod()方法反砌。
- 在屬性someProperty的 getter 或 setter 的重寫實現(xiàn)中雾鬼,可以通過super.someProperty來訪問超類版本的someProperty屬性。
- 在下標的重寫實現(xiàn)中宴树,可以通過super[someIndex]來訪問超類版本中的相同下標策菜。
重寫方法
在子類中,你可以重寫繼承來的實例方法或類方法酒贬,提供一個定制或替代的方法實現(xiàn)又憨。
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
重寫屬性
你可以重寫繼承來的實例屬性或類型屬性,提供自己定制的 getter 和 setter同衣,或添加屬性觀察器使重寫的屬性可以觀察屬性值什么時候發(fā)生改變竟块。
重寫屬性的 Getters 和 Setters
你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性壶运,無論繼承來的屬性是存儲型的還是計算型的屬性耐齐。子類并不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型蒋情。你在重寫一個屬性時埠况,必需將它的名字和類型都寫出來。這樣才能使編譯器去檢查你重寫的屬性是與超類中同名同類型的屬性相匹配的棵癣。
你可以將一個繼承來的只讀屬性重寫為一個讀寫屬性辕翰,只需要在重寫版本的屬性里提供 getter 和 setter 即可。但是狈谊,你不可以將一個繼承來的讀寫屬性重寫為一個只讀屬性喜命。
注意 如果你在重寫屬性中提供了 setter沟沙,那么你也一定要提供 getter。如果你不想在重寫版本中的 getter 里修改繼承來的屬性值壁榕,你可以直接通過super.someProperty來返回繼承來的值矛紫,其中someProperty是你要重寫的屬性的名字。
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
重寫屬性觀察器
你可以通過重寫屬性為一個繼承來的屬性添加屬性觀察器牌里。這樣一來颊咬,當繼承來的屬性值發(fā)生改變時,你就會被通知到牡辽,無論那個屬性原本是如何實現(xiàn)的喳篇。關于屬性觀察器的更多內容,請看屬性觀察器态辛。
注意 你不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器麸澜。這些屬性的值是不可以被設置的,所以奏黑,為它們提供willSet或didSet實現(xiàn)是不恰當痰憎。 此外還要注意,你不可以同時提供重寫的 setter 和重寫的屬性觀察器攀涵。如果你想觀察屬性值的變化铣耘,并且你已經(jīng)為那個屬性提供了定制的 setter,那么你在 setter 中就可以觀察到任何值變化了以故。
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
防止重寫
你可以通過把方法蜗细,屬性或下標標記為final來防止它們被重寫,只需要在聲明關鍵字前加上final修飾符即可(例如:final var怒详,final func炉媒,final class func,以及final subscript)昆烁。
如果你重寫了帶有final標記的方法吊骤、屬性或下標,在編譯時會報錯静尼。在類擴展中的方法白粉,屬性或下標也可以在擴展的定義里標記為 final 的。
你可以通過在關鍵字class前添加final修飾符(final class)來將整個類標記為 final 的鼠渺。這樣的類是不可被繼承的鸭巴,試圖繼承這樣的類會導致編譯報錯。
構造過程
你可以在構造器中為存儲型屬性賦初值拦盹,也可以在定義屬性時為其設置默認值鹃祖。
構造過程是使用類、結構體或枚舉類型的實例之前的準備過程普舆。在新實例可用前必須執(zhí)行這個過程恬口,具體操作包括設置實例中每個存儲型屬性的初始值和執(zhí)行其他必須的設置或初始化工作校读。
通過定義構造器來實現(xiàn)構造過程,就像用來創(chuàng)建特定類型新實例的特殊方法祖能。與 Objective-C 中的構造器不同地熄,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化芯杀。
注:類和結構體在創(chuàng)建實例時端考,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處于一個未知的狀態(tài)揭厚。
你可以在構造器中為存儲型屬性賦初值却特,也可以在定義屬性時為其設置默認值。
構造參數(shù)
自定義構造過程時筛圆,可以在定義中提供構造參數(shù)裂明,指定參數(shù)值的類型和名字。構造參數(shù)的功能和語法跟函數(shù)和方法的參數(shù)相同太援。
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
//不帶外部名的構造器參數(shù)
init(_ celsius: Double){
temperatureInCelsius = celsius
}
}
可選屬性類型
如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性——無論是因為它無法在初始化時賦值闽晦,還是因為它在之后某個時間點可以賦值為空——你都需要將它定義為可選類型√岵恚可選類型的屬性將自動初始化為 nil仙蛉,表示這個屬性是有意在初始化時設置為空的。
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
}
默認構造器
如果結構體或類的所有屬性都有默認值碱蒙,同時沒有自定義的構造器偿警,那么 Swift 會給這些結構體或類提供一個默認構造器(default initializers)移国。這個默認構造器將簡單地創(chuàng)建一個所有屬性值都設置為默認值的實例。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
結構體的逐一成員構造器:除了上面提到的默認構造器嘱能,如果結構體沒有提供自定義的構造器英妓,它們將自動獲得一個逐一成員構造器彻桃,即使結構體的存儲型屬性沒有默認值晒屎。
逐一成員構造器是用來初始化結構體新實例里成員屬性的快捷方法肄渗。我們在調用逐一成員構造器時,通過與成員屬性名相同的參數(shù)名進行傳值來完成對成員屬性的初始賦值季惯。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
指定構造器和便利構造器
類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構造過程中設置初始值Swift 為類類型提供了兩種構造器來確保實例中所有存儲型屬性都能獲得初始值吠各,它們分別是指定構造器和便利構造器。
指定構造器是類中最主要的構造器星瘾。一個指定構造器將初始化類中提供的所有屬性走孽,并根據(jù)父類鏈往上調用父類合適的構造器來實現(xiàn)父類的初始化。
類傾向于擁有少量指定構造器琳状,普遍的是一個類擁有一個指定構造器。指定構造器在初始化的地方通過“管道”將初始化過程持續(xù)到父類鏈盒齿。
每一個類都必須至少擁有一個指定構造器念逞。在某些情況下困食,許多類通過繼承了父類中的指定構造器而滿足了這個條件。具體內容請參考后續(xù)章節(jié)構造器的自動繼承翎承。
便利構造器是類中比較次要的硕盹、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器叨咖,并為其參數(shù)提供默認值瘩例。你也可以定義便利構造器來創(chuàng)建一個特殊用途或特定輸入值的實例。
你應當只在必要的時候為類提供便利構造器甸各,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器垛贤,能夠節(jié)省更多開發(fā)時間并讓類的構造過程更清晰明了。
類的構造器代理規(guī)則
為了簡化指定構造器和便利構造器之間的調用關系趣倾,Swift 采用以下三條規(guī)則來限制構造器之間的代理調用:
- 指定構造器必須調用其直接父類的的指定構造器聘惦。
- 便利構造器必須調用同類中定義的其它構造器。
- 便利構造器最后必須調用指定構造器儒恋。
一個更方便記憶的方法是:
- 指定構造器必須總是向上代理
- 便利構造器必須總是橫向代理
[圖片上傳失敗...(image-6e68cd-1524110403362)]
如圖所示善绎,父類中包含一個指定構造器和兩個便利構造器。其中一個便利構造器調用了另外一個便利構造器诫尽,而后者又調用了唯一的指定構造器禀酱。這滿足了上面提到的規(guī)則 2 和 3。這個父類沒有自己的父類牧嫉,所以規(guī)則 1 沒有用到比勉。
子類中包含兩個指定構造器和一個便利構造器。便利構造器必須調用兩個指定構造器中的任意一個驹止,因為它只能調用同一個類里的其他構造器浩聋。這滿足了上面提到的規(guī)則 2 和 3。而兩個指定構造器必須調用父類中唯一的指定構造器臊恋,這滿足了規(guī)則 1衣洁。
[圖片上傳失敗...(image-8256f2-1524110403362)]
兩段式構造過程
Swift 中類的構造過程包含兩個階段。第一個階段抖仅,類中的每個存儲型屬性賦一個初始值坊夫。當每個存儲型屬性的初始值被賦值后,第二階段開始撤卢,它給每個類一次機會环凿,在新實例準備使用之前進一步定制它們的存儲型屬性。
兩段式構造過程的使用讓構造過程更安全放吩,同時在整個類層級結構中給予了每個類完全的靈活性智听。兩段式構造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個構造器意外地賦予不同的值。
Swift 編譯器將執(zhí)行 4 種有效的安全檢查到推,以確保兩段式構造過程不出錯地完成:
- 安全檢查 1
指定構造器必須保證它所在類的所有屬性都必須先初始化完成考赛,之后才能將其它構造任務向上代理給父類中的構造器。
如上所述莉测,一個對象的內存只有在其所有存儲型屬性確定之后才能完全初始化颜骤。為了滿足這一規(guī)則,指定構造器必須保證它所在類的屬性在它往上代理之前先完成初始化捣卤。
- 安全檢查 2
指定構造器必須在為繼承的屬性設置新值之前向上代理調用父類構造器忍抽,如果沒這么做,指定構造器賦予的新值將被父類中的構造器所覆蓋董朝。
- 安全檢查 3
便利構造器必須為任意屬性(包括同類中定義的)賦新值之前代理調用同一類中的其它構造器鸠项,如果沒這么做,便利構造器賦予的新值將被同一類中其它指定構造器所覆蓋益涧。
- 安全檢查 4
構造器在第一階段構造完成之前锈锤,不能調用任何實例方法,不能讀取任何實例屬性的值闲询,不能引用self作為一個值久免。
類實例在第一階段結束以前并不是完全有效的。只有第一階段完成后扭弧,該實例才會成為有效實例阎姥,才能訪問屬性和調用方法。
構造器的繼承和重寫
跟 Objective-C 中的子類不同鸽捻,Swift 中的子類默認情況下不會繼承父類的構造器呼巴。Swift 的這種機制可以防止一個父類的簡單構造器被一個更精細的子類繼承,并被錯誤地用來創(chuàng)建子類的實例御蒲。
假如你希望自定義的子類中能提供一個或多個跟父類相同的構造器衣赶,你可以在子類中提供這些構造器的自定義實現(xiàn)。
當你在編寫一個和父類中指定構造器相匹配的子類構造器時厚满,你實際上是在重寫父類的這個指定構造器府瞄。因此,你必須在定義子類構造器時帶上 override 修飾符碘箍。即使你重寫的是系統(tǒng)自動提供的默認構造器遵馆,也需要帶上 override 修飾符。
正如重寫屬性丰榴,方法或者是下標货邓,override 修飾符會讓編譯器去檢查父類中是否有相匹配的指定構造器,并驗證構造器參數(shù)是否正確四濒。
構造器的自動繼承
如上所述换况,子類在默認情況下不會繼承父類的構造器职辨。但是如果滿足特定條件,父類構造器是可以被自動繼承的复隆。事實上拨匆,這意味著對于許多常見場景你不必重寫父類的構造器姆涩,并且可以在安全的情況下以最小的代價繼承父類的構造器挽拂。
假設你為子類中引入的所有新屬性都提供了默認值,以下 2 個規(guī)則適用:
- 規(guī)則 1
如果子類沒有定義任何指定構造器骨饿,它將自動繼承父類所有的指定構造器亏栈。
- 規(guī)則 2
如果子類提供了所有父類指定構造器的實現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實現(xiàn)(例如:類本身的便利構造器和父類的指定構造器重名)——它將自動繼承父類所有的便利構造器宏赘。
即使你在子類中添加了更多的便利構造器绒北,這兩條規(guī)則仍然適用。
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ?" : " ?"
return output
}
}
[圖片上傳失敗...(image-f6c4fd-1524110403362)]
可失敗構造器
如果一個類察署、結構體或枚舉類型的對象闷游,在構造過程中有可能失敗,則為其定義一個可失敗構造器是很有用的贴汪。這里所指的“失敗” 指的是脐往,如給構造器傳入無效的參數(shù)值,或缺少某種所需的外部資源扳埂,又或是不滿足某種必要的條件等业簿。
為了妥善處理這種構造過程中可能會失敗的情況。你可以在一個類阳懂,結構體或是枚舉類型的定義中梅尤,添加一個或多個可失敗構造器。其語法為在 init 關鍵字后面添加問號 (init?)岩调。
可失敗構造器會創(chuàng)建一個類型為自身類型的可選類型的對象巷燥。你通過 return nil 語句來表明可失敗構造器在何種情況下應該 “失敗”。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
let someCreature = Animal(species: "Giraffe")
// someCreature 的類型是 Animal? 而不是 Animal
枚舉類型的可失敗構造器
你可以通過一個帶一個或多個參數(shù)的可失敗構造器來獲取枚舉類型中特定的枚舉成員号枕。如果提供的參數(shù)無法匹配任何枚舉成員缰揪,則構造失敗。
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
帶原始值的枚舉類型的可失敗構造器
帶原始值的枚舉類型會自帶一個可失敗構造器 init?(rawValue:)堕澄,該可失敗構造器有一個名為 rawValue 的參數(shù)邀跃,其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個枚舉成員的原始值匹配蛙紫,則該構造器會構造相應的枚舉成員拍屑,否則構造失敗。
enum TemperatureUnit: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
構造失敗的傳遞
類坑傅,結構體僵驰,枚舉的可失敗構造器可以橫向代理到同類型中的其他可失敗構造器。類似的,子類的可失敗構造器也能向上代理到父類的可失敗構造器蒜茴。
無論是向上代理還是橫向代理星爪,如果你代理到的其他可失敗構造器觸發(fā)構造失敗,整個構造過程將立即終止粉私,接下來的任何構造代碼不會再被執(zhí)行顽腾。
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}
必要構造器
在類的構造器前添加 required 修飾符表明所有該類的子類都必須實現(xiàn)該構造器:
class SomeClass {
required init() {
// 構造器的實現(xiàn)代碼
}
}
在子類重寫父類的必要構造器時,必須在子類的構造器前也添加 required 修飾符诺核,表明該構造器要求也應用于繼承鏈后面的子類抄肖。在重寫父類中必要的指定構造器時,不需要添加 override 修飾符:
class SomeSubclass: SomeClass {
required init() {
// 構造器的實現(xiàn)代碼
}
}
通過閉包或函數(shù)設置屬性的默認值
如果某個存儲型屬性的默認值需要一些定制或設置窖杀,你可以使用閉包或全局函數(shù)為其提供定制的默認值漓摩。每當某個屬性所在類型的新實例被創(chuàng)建時,對應的閉包或函數(shù)會被調用入客,而它們的返回值會當做默認值賦值給這個屬性管毙。
這種類型的閉包或函數(shù)通常會創(chuàng)建一個跟屬性類型相同的臨時變量,然后修改它的值以滿足預期的初始狀態(tài)桌硫,最后返回這個臨時變量夭咬,作為屬性的默認值。
class SomeClass {
let someProperty: SomeType = {
// 在這個閉包中給 someProperty 創(chuàng)建一個默認值
// someValue 必須和 SomeType 類型相同
return someValue
}()
}
相對于計算屬性每次返回相同一樣的值鞍泉,閉包只是一次性賦值皱埠,后續(xù)可更改
可選鏈式調用
可選鏈式調用是一種可以在當前值可能為nil的可選值上請求和調用屬性、方法及下標的方法咖驮。如果可選值有值边器,那么調用就會成功;如果可選值是nil托修,那么調用將返回nil忘巧。多個調用可以連接在一起形成一個調用鏈,如果其中任何一個節(jié)點為nil睦刃,整個調用鏈都會失敗砚嘴,即返回nil。
Swift 的可選鏈式調用和 Objective-C 中向nil發(fā)送消息有些相像涩拙,但是 Swift 的可選鏈式調用可以應用于任意類型际长,并且能檢查調用是否成功。
使用可選鏈式調用代替強制展開
通過在想調用的屬性兴泥、方法工育、或下標的可選值后面放一個問號(?),可以定義一個可選鏈搓彻。這一點很像在可選值后面放一個嘆號(!)來強制展開它的值如绸。它們的主要區(qū)別在于當可選值為空時可選鏈式調用只會調用失敗嘱朽,然而強制展開將會觸發(fā)運行時錯誤。
可選鏈式調用的返回結果與原本的返回結果具有相同的類型怔接,但是被包裝成了一個可選值搪泳。例如,使用可選鏈式調用訪問屬性扼脐,當可選鏈式調用成功時岸军,如果屬性原本的返回結果是Int類型,則會變?yōu)镮nt?類型谎势。
通過可選鏈式調用訪問屬性
正如使用可選鏈式調用代替強制展開中所述凛膏,可以通過可選鏈式調用在一個可選值上訪問它的屬性杨名,并判斷訪問是否成功脏榆。通過可選鏈式調用調用方法
可以通過可選鏈式調用來調用方法,并判斷是否調用成功台谍,即使這個方法沒有返回值须喂。
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
這個方法沒有返回值。然而趁蕊,沒有返回值的方法具有隱式的返回類型Void坞生,如無返回值函數(shù)中所述。這意味著沒有返回值的方法也會返回()掷伙,或者說空的元組是己。
如果在可選值上通過可選鏈式調用來調用這個方法,該方法的返回類型會是Void?任柜,而不是Void卒废,因為通過可選鏈式調用得到的返回值都是可選的。這樣我們就可以使用if語句來判斷能否成功調用printNumberOfRooms()方法宙地,即使方法本身沒有定義返回值摔认。通過判斷返回值是否為nil可以判斷調用是否成功:
- 通過可選鏈式調用訪問下標
通過可選鏈式調用,我們可以在一個可選值上訪問下標宅粥,并且判斷下標調用是否成功参袱。
連接多層可選鏈式調用
可以通過連接多個可選鏈式調用在更深的模型層級中訪問屬性、方法以及下標秽梅。然而抹蚀,多層可選鏈式調用不會增加返回值的可選層級。
也就是說:
- 如果你訪問的值不是可選的企垦,可選鏈式調用將會返回可選值环壤。
- 如果你訪問的值就是可選的,可選鏈式調用不會讓可選返回值變得“更可選”竹观。
因此: - 通過可選鏈式調用訪問一個Int值镐捧,將會返回Int?潜索,無論使用了多少層可選鏈式調用。
- 類似的懂酱,通過可選鏈式調用訪問Int?值竹习,依舊會返回Int?值,并不會返回Int??列牺。
在方法的可選返回值上進行可選鏈式調用
我們可以在一個可選值上通過可選鏈式調用來調用方法整陌,并且可以根據(jù)需要繼續(xù)在方法的可選返回值上進行可選鏈式調用
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// 打印 “John's building identifier begins with "The".”
錯誤處理
錯誤處理(Error handling)是響應錯誤以及從錯誤中恢復的過程。Swift 提供了在運行時對可恢復錯誤的拋出瞎领、捕獲泌辫、傳遞和操作的一等公民支持。
表示并拋出錯誤
在 Swift 中九默,錯誤用符合Error協(xié)議的類型的值來表示震放。這個空協(xié)議表明該類型可以用于錯誤處理。
Swift 的枚舉類型尤為適合構建一組相關的錯誤狀態(tài)驼修,枚舉的關聯(lián)值還可以提供錯誤狀態(tài)的額外信息殿遂。例如,你可以這樣表示在一個游戲中操作自動販賣機時可能會出現(xiàn)的錯誤狀態(tài):
enum VendingMachineError: Error {
case invalidSelection //選擇無效
case insufficientFunds(coinsNeeded: Int) //金額不足
case outOfStock //缺貨
}
拋出一個錯誤可以讓你表明有意外情況發(fā)生乙各,導致正常的執(zhí)行流程無法繼續(xù)執(zhí)行墨礁。拋出錯誤使用throw關鍵字。例如耳峦,下面的代碼拋出一個錯誤恩静,提示販賣機還需要5個硬幣:
throw VendingMachineError. insufficientFunds(coinsNeeded: 5)
用 throwing 函數(shù)傳遞錯誤
為了表示一個函數(shù)、方法或構造器可以拋出錯誤蹲坷,在函數(shù)聲明的參數(shù)列表之后加上throws關鍵字驶乾。一個標有throws關鍵字的函數(shù)被稱作throwing 函數(shù)。如果這個函數(shù)指明了返回值類型冠句,throws關鍵詞需要寫在箭頭(->)的前面轻掩。
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
用 Do-Catch 處理錯誤
可以使用一個do-catch語句運行一段閉包代碼來處理錯誤。如果在do子句中的代碼拋出了一個錯誤懦底,這個錯誤會與catch子句做匹配唇牧,從而決定哪條子句能處理它。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
指定清理操作
可以使用defer語句在即將離開當前代碼塊時執(zhí)行一系列語句聚唐。該語句讓你能執(zhí)行一些必要的清理工作丐重,不管是以何種方式離開當前代碼塊的——無論是由于拋出錯誤而離開,或是由于諸如return杆查、break的語句扮惦。例如,你可以用defer語句來確保文件描述符得以關閉亲桦,以及手動分配的內存得以釋放崖蜜。
defer語句將代碼的執(zhí)行延遲到當前的作用域退出之前浊仆。該語句由defer關鍵字和要被延遲執(zhí)行的語句組成。延遲執(zhí)行的語句不能包含任何控制轉移語句豫领,例如break抡柿、return語句,或是拋出一個錯誤等恐。延遲執(zhí)行的操作會按照它們聲明的順序從后往前執(zhí)行——也就是說洲劣,第一條defer語句中的代碼最后才執(zhí)行,第二條defer語句中的代碼倒數(shù)第二個執(zhí)行课蔬,以此類推囱稽。最后一條語句會第一個執(zhí)行
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件。
}
// close(file) 會在這里被調用,即作用域的最后。
}
}
類型轉換
類型轉換 可以判斷實例的類型钠绍,也可以將實例看做是其父類或者子類的實例。
類型轉換在 Swift 中使用 is 和 as 操作符實現(xiàn)样傍。這兩個操作符提供了一種簡單達意的方式去檢查值的類型或者轉換它的類型。
檢查類型
用類型檢查操作符(is)來檢查一個實例是否屬于特定子類型铺遂。若實例屬于那個子類型,類型檢查操作符返回 true茎刚,否則返回 false襟锐。
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs”
向下轉型
某類型的一個常量或變量可能在幕后實際上屬于一個子類。當確定是這種情況時膛锭,你可以嘗試向下轉到它的子類型粮坞,用類型轉換操作符(as? 或 as!)。
因為向下轉型可能會失敗初狰,類型轉型操作符帶有兩種不同形式莫杈。條件形式as? 返回一個你試圖向下轉成的類型的可選值。強制形式 as! 把試圖向下轉型和強制解包轉換結果結合為一個操作奢入。
當你不確定向下轉型可以成功時筝闹,用類型轉換的條件形式(as?)。條件形式的類型轉換總是返回一個可選值腥光,并且若下轉是不可能的关顷,可選值將是 nil。這使你能夠檢查向下轉型是否成功武福。
只有你可以確定向下轉型一定會成功時议双,才使用強制形式(as!)。當你試圖向下轉型為一個不正確的類型時捉片,強制形式的類型轉換會觸發(fā)一個運行時錯誤
for item in library {
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: '\(song.name)', by \(song.artist)")
}
}
// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley
Any 和 AnyObject 的類型轉換
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
things 數(shù)組包含兩個 Int 值平痰,兩個 Double 值汞舱,一個 String 值,一個元組 (Double, Double)宗雇,一個Movie實例“Ghostbusters”兵拢,以及一個接受 String 值并返回另一個 String 值的閉包表達式。
你可以在 switch 表達式的 case 中使用 is 和 as 操作符來找出只知道是 Any 或 AnyObject 類型的常量或變量的具體類型逾礁。下面的示例迭代 things 數(shù)組中的每一項说铃,并用 switch 語句查找每一項的類型。有幾個 switch 語句的 case 綁定它們匹配到的值到一個指定類型的常量嘹履,從而可以打印這些值:
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as String -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
// Hello, Michael
擴展(Extensions)
擴展 就是為一個已有的類腻扇、結構體、枚舉類型或者協(xié)議類型添加新功能砾嫉。這包括在沒有權限獲取原始源代碼的情況下擴展類型的能力(即 逆向建模 )幼苛。擴展和 Objective-C 中的分類類似。(與 Objective-C 不同的是焕刮,Swift 的擴展沒有名字舶沿。)
Swift 中的擴展可以:
- 添加計算型屬性和計算型類型屬性
- 定義實例方法和類型方法
- 提供新的構造器
- 定義下標
- 定義和使用新的嵌套類型
- 使一個已有類型符合某個協(xié)議
擴展語法
使用關鍵字 extension 來聲明擴展:
extension SomeType {
// 為 SomeType 添加的新功能寫到這里
}
extension SomeType: SomeProtocol, AnotherProctocol {
// 協(xié)議實現(xiàn)寫到這里
}
可變實例方法
通過擴展添加的實例方法也可以修改該實例本身。結構體和枚舉類型中修改 self 或其屬性的方法必須將該實例方法標注為 mutating配并,正如來自原始實現(xiàn)的可變方法一樣括荡。
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt 的值現(xiàn)在是 9
協(xié)議
協(xié)議 定義了一個藍圖,規(guī)定了用來實現(xiàn)某一特定任務或者功能的方法溉旋、屬性畸冲,以及其他需要的東西。類观腊、結構體或枚舉都可以遵循協(xié)議邑闲,并為協(xié)議定義的這些要求提供具體實現(xiàn)。某個類型能夠滿足某個協(xié)議的要求梧油,就可以說該類型遵循這個協(xié)議苫耸。
協(xié)議語法
協(xié)議的定義方式與類、結構體和枚舉的定義非常相似:
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結構體的定義部分
}
擁有父類的類在遵循協(xié)議時儡陨,應該將父類名放在協(xié)議名之前褪子,以逗號分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
屬性要求
協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實例屬性或類型屬性。協(xié)議不指定屬性是存儲型屬性還是計算型屬性迄委,它只指定屬性的名稱和類型褐筛。此外,協(xié)議還指定屬性是可讀的還是可讀可寫的叙身。
如果協(xié)議要求屬性是可讀可寫的渔扎,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協(xié)議只要求屬性是可讀的信轿,那么該屬性不僅可以是可讀的晃痴,如果代碼需要的話残吩,還可以是可寫的。
協(xié)議總是用 var 關鍵字來聲明變量屬性倘核,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的泣侮,可讀屬性則用 { get } 來表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協(xié)議中定義類型屬性時,總是使用 static 關鍵字作為前綴紧唱。當類類型遵循協(xié)議時活尊,除了 static 關鍵字,還可以使用 class 關鍵字來聲明類型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
方法要求
協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)某些指定的實例方法或類方法漏益。這些方法作為協(xié)議的一部分蛹锰,像普通方法一樣放在協(xié)議的定義中,但是不需要大括號和方法體绰疤⊥可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同轻庆。但是癣猾,不支持為協(xié)議中的方法的參數(shù)提供默認值。
正如屬性要求中所述余爆,在協(xié)議中定義類方法的時候纷宇,總是使用 static 關鍵字作為前綴。當類類型遵循協(xié)議時龙屉,除了 static 關鍵字呐粘,還可以使用 class 關鍵字作為前綴:
protocol SomeProtocol {
static func someTypeMethod()
}
實例方法:
protocol RandomNumberGenerator {
func random() -> Double
}
Mutating 方法要求
有時需要在方法中改變方法所屬的實例。例如转捕,在值類型(即結構體和枚舉)的實例方法中,將 mutating 關鍵字作為方法的前綴唆垃,寫在 func 關鍵字之前五芝,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值。這一過程在在實例方法中修改值類型章節(jié)中有詳細描述辕万。
如果你在協(xié)議中定義了一個實例方法枢步,該方法會改變遵循該協(xié)議的類型的實例,那么在定義協(xié)議時需要在方法前加 mutating 關鍵字渐尿。這使得結構體和枚舉能夠遵循此協(xié)議并滿足此方法要求醉途。
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
構造器要求
協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)指定的構造器。你可以像編寫普通構造器那樣砖茸,在協(xié)議的定義里寫下構造器的聲明隘擎,但不需要寫花括號和構造器的實體:
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 這里是構造器的實現(xiàn)部分
}
}
如果一個子類重寫了父類的指定構造器,并且該構造器滿足了某個協(xié)議的要求凉夯,那么該構造器的實現(xiàn)需要同時標注 required 和 override 修飾符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 這里是構造器的實現(xiàn)部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因為遵循協(xié)議货葬,需要加上 required
// 因為繼承自父類采幌,需要加上 override
required override init() {
// 這里是構造器的實現(xiàn)部分
}
}
協(xié)議作為類型
盡管協(xié)議本身并未實現(xiàn)任何功能,但是協(xié)議可以被當做一個成熟的類型來使用震桶。
協(xié)議可以像其他普通類型一樣使用休傍,使用場景如下:
- 作為函數(shù)、方法或構造器中的參數(shù)類型或返回值類型
- 作為常量蹲姐、變量或屬性的類型
- 作為數(shù)組磨取、字典或其他容器中的元素類型
class LinearCongruentialGenerator : RandomNumberGenerator{
}
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> RandomNumberGenerator {
return LinearCongruentialGenerator()
}
}
委托(代理)模式
委托是一種設計模式,它允許類或結構體將一些需要它們負責的功能委托給其他類型的實例柴墩。委托模式的實現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能忙厌,這樣就能確保遵循協(xié)議的類型能提供這些功能。委托模式可以用來響應特定的動作拐邪,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù)慰毅,而無需關心外部數(shù)據(jù)源的類型。
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGame)
}
通過擴展添加協(xié)議一致性
即便無法修改源代碼扎阶,依然可以通過擴展令已有類型遵循并符合協(xié)議汹胃。擴展可以為已有類型添加屬性、方法东臀、下標以及構造器着饥,因此可以符合協(xié)議中的相應要求。
protocol TextRepresentable {
var textualDescription: String { get }
}
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
通過擴展遵循協(xié)議
當一個類型已經(jīng)符合了某個協(xié)議中的所有要求惰赋,卻還沒有聲明遵循該協(xié)議時宰掉,可以通過空擴展體的擴展來遵循該協(xié)議:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
//從現(xiàn)在起,Hamster 的實例可以作為 TextRepresentable 類型使用:
協(xié)議類型的集合
協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用赁濒,在協(xié)議類型提到了這樣的用法轨奄。下面的例子創(chuàng)建了一個元素類型為 TextRepresentable 的數(shù)組:
let things: [TextRepresentable] = [game, d12, simonTheHamster]
協(xié)議的繼承
協(xié)議能夠繼承一個或多個其他協(xié)議,可以在繼承的協(xié)議的基礎上增加新的要求拒炎。協(xié)議的繼承語法與類的繼承相似挪拟,多個被繼承的協(xié)議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協(xié)議的定義部分
}
類類型專屬協(xié)議
你可以在協(xié)議的繼承列表中,通過添加 class 關鍵字來限制協(xié)議只能被類類型遵循击你,而結構體或枚舉不能遵循該協(xié)議玉组。class 關鍵字必須第一個出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類類型專屬協(xié)議的定義部分
}
協(xié)議合成
有時候需要同時遵循多個協(xié)議丁侄,你可以將多個協(xié)議采用 SomeProtocol & AnotherProtocol 這樣的格式進行組合惯雳,稱為 協(xié)議合成(protocol composition)。你可以羅列任意多個你想要遵循的協(xié)議鸿摇,以與符號(&)分隔石景。
下面的例子中,將 Named 和 Aged 兩個協(xié)議按照上述語法組合成一個協(xié)議,作為函數(shù)參數(shù)的類型:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”
檢查協(xié)議一致性
你可以使用類型轉換中描述的 is 和 as 操作符來檢查協(xié)議一致性鸵钝,即是否符合某協(xié)議糙臼,并且可以轉換到指定的協(xié)議類型。檢查和轉換到某個協(xié)議類型在語法上和類型的檢查和轉換完全相同:
- is 用來檢查實例是否符合某個協(xié)議恩商,若符合則返回 true变逃,否則返回 false。
- as? 返回一個可選值怠堪,當實例符合某個協(xié)議時揽乱,返回類型為協(xié)議類型的可選值,否則返回 nil粟矿。
- as! 將實例強制向下轉換到某個協(xié)議類型凰棉,如果強轉失敗,會引發(fā)運行時錯誤陌粹。
可選的協(xié)議要求
協(xié)議可以定義可選要求撒犀,遵循協(xié)議的類型可以選擇是否實現(xiàn)這些要求。在協(xié)議中使用 optional 關鍵字作為前綴來定義可選要求掏秩』蛭瑁可選要求用在你需要和 Objective-C 打交道的代碼中。協(xié)議和可選要求都必須帶上@objc屬性蒙幻。標記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 @objc 類遵循映凳,其他類以及結構體和枚舉均不能遵循這種協(xié)議。
使用可選要求時(例如邮破,可選的方法或者屬性)诈豌,它們的類型會自動變成可選的。比如抒和,一個類型為 (Int) -> String 的方法會變成 ((Int) -> String)?矫渔。需要注意的是整個函數(shù)類型是可選的,而不是函數(shù)的返回值摧莽。
協(xié)議中的可選要求可通過可選鏈式調用來使用蚌斩,因為遵循協(xié)議的類型可能沒有實現(xiàn)這些可選要求。類似 someOptionalMethod?(someArgument) 這樣范嘱,你可以在可選方法名稱后加上 ? 來調用可選方法。
協(xié)議擴展
協(xié)議可以通過擴展來為遵循協(xié)議的類型提供屬性员魏、方法以及下標的實現(xiàn)丑蛤。通過這種方式,你可以基于協(xié)議本身來實現(xiàn)這些功能撕阎,而無需在每個遵循協(xié)議的類型中都重復同樣的實現(xiàn)受裹,也無需使用全局函數(shù)。
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
提供默認實現(xiàn)
可以通過協(xié)議擴展來為協(xié)議要求的屬性、方法以及下標提供默認的實現(xiàn)棉饶。如果遵循協(xié)議的類型為這些要求提供了自己的實現(xiàn)厦章,那么這些自定義實現(xiàn)將會替代擴展中的默認實現(xiàn)被使用。
通過協(xié)議擴展為協(xié)議要求提供的默認實現(xiàn)和可選的協(xié)議要求不同照藻。雖然在這兩種情況下袜啃,遵循協(xié)議的類型都無需自己實現(xiàn)這些要求,但是通過擴展提供的默認實現(xiàn)可以直接調用幸缕,而無需使用可選鏈式調用群发。
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
為協(xié)議擴展添加限制條件
在擴展協(xié)議的時候,可以指定一些限制條件发乔,只有遵循協(xié)議的類型滿足這些限制條件時熟妓,才能獲得協(xié)議擴展提供的默認實現(xiàn)。這些限制條件寫在協(xié)議名之后栏尚,使用 where 子句來描述起愈,正如Where子句中所描述的。
extension Collection where Iterator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
如果多個協(xié)議擴展都為同一個協(xié)議要求提供了默認實現(xiàn)译仗,而遵循協(xié)議的類型又同時滿足這些協(xié)議擴展的限制條件抬虽,那么將會使用限制條件最多的那個協(xié)議擴展提供的默認實現(xiàn)。