1.前言:
Swift
包含了 C
和 Objective-C
上所有基礎數據類型,Int
表示整型值; Double
和 Float
表示浮點型值; Bool
是布爾型值;String
是文本型數據。 Swift
還提供了三個基本的集合類型,Array
,Set
和 Dictionary
。
在 Swift
中,廣泛的使用著值不可變的變量,它們就是常量。而且比 C
語言的常量更強大。在 Swift
中,如果你要處理的值不需要改變,那使用常量可以讓你的代碼更加安全并且更清晰地表達你的意圖。
Swift
是一門類型安全
的語言,可選類型
就是一個很好的例子。
2.字符串插值
Swift
用字符串插值(string interpolation
)的方式把常量名或者變量名當做占位符加入到長字符串中,Swift
會用當前常量或變量的值替換這些占位符。
var friendlyWelcome = "朋友你好!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 輸出 "The current value of friendlyWelcome is Bonjour!
-
類型推斷
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測為類型 "Int?", 或者類型 "optional Int"
如果possibleNumber
是"asdas,那么convertedNumber
就是nil
4.nil
你可以給可選變量賦值為nil
來表示它沒有值:
var surveyAnswer: String?
// surveyAnswer 被自動設置為 nil
Swift
的 nil
和 Objective-C
中的 nil
并不一樣。在 Objective-C
中,nil
是一個指向不存在對象的指針。在 Swift
中,nil
不是指針——它是一個確定的值,用來表示值缺失
。任何類型的可選狀態都可以被設置為 nil
,不只是對象類型。
5.隱式解析可選類型
把想要用作可選的類型的后面的問號(String?
)改成感嘆號(String!
)來聲明一個隱式解析可選類型。
可以把隱式解析可選類型當做一個可以自動解析的可選類型。你要做的只是聲明的時候把感嘆號放到類型的結尾,而不是每次取值的可選名字的結尾。
6.錯誤處理
你可以使用 錯誤處理(error handling
) 來應對程序執行中可能會遇到的錯誤條件。
1)函數throws
7.集合類型
Swift
語言提供Arrays
、Sets
和Dictionaries
三種基本的集合類型用來存儲集合數據。數組(Arrays
)是有序數據的集。集合(Sets
)是無序無重復數據的集。字典(Dictionaries
)是無序的鍵值對的集。
Swift
的Arrays
、Sets
和Dictionaries
類型被實現為泛型
集合。
在我們不需要改變集合的時候創建不可變集合是很好的實踐。如此 Swift
編譯器可以優化我們創建的集合。 Swift
的Array
類型被橋接到Foundation
中的NSArray
類
1)數組
通過兩個數組相加創建一個數組
var threeDoubles = [Double](repeating: 0.0, count: 3)
var anotherThreeDoubles = [Double](repeatElement(0.125, count: 3))
var sixDoubles = threeDoubles + anotherThreeDoubles
可以使用數組的只讀屬性count
來獲取數組中的數據項數量:
2)集合(Sets)
集合(Set
)用來存儲相同類型并且沒有確定順序的值。當集合元素順序不重要
時或者希望確保每個元素只出現一次
時可以使用集合而不是數組。
Swift
的Set
類型被橋接到Foundation
中的NSSet
類。
集合類型的哈希值
一個類型為了存儲在集合
中,該類型必須是可哈希化
的
一個類型為了存儲在集合中,該類型必須是可哈希化
的--也就是說,該類型必須提供一個方法來計算它的哈希值。一個哈希值是Int
類型的,相等的對象哈希值必須相同,比如a==b
,因此必須a.hashValue == b.hashValue
。
Swift
的所有基本類型(比如String,Int,Double和Bool
)默認都是可哈希化
的,可以作為集合的值的類型或者字典的鍵的類型。沒有關聯值的枚舉成員值(在枚舉有講述)默認也是可哈希化的。
Swift
中的Set
類型被寫為Set<Element>
,這里的Element
表示Set
中允許存儲的類型,和數組不同的是,集合沒有等價的簡化形式。
*注意:集合要聯想起我們高中數學的集合。
使用intersect(_:)
方法根據兩個集合中都包含的值創建的一個新的集合。
使用exclusiveOr(_:)
方法根據在一個集合中但不在兩個集合中的值創建一個新的集合。
使用union(_:)
方法根據兩個集合的值創建一個新的集合。
使用subtract(_:)
方法根據不在該集合中的值創建一個新的集合。
3)字典
Swift
的Dictionary
類型被橋接到Foundation
的NSDictionary
類。
字典是一種存儲多個相同類型的值的容器。每個值(value
)都關聯唯一的鍵(key
),鍵作為字典中的這個值數據的標識符。和數組中的數據項不同,字典中的數據項并沒有具體順序。我們在需要通過標識符(鍵)訪問數據的時候使用字典,這種方法很大程度上和我們在現實世界中使用字典查字義的方法一樣。
Swift
的字典使用Dictionary<Key, Value>
定義,其中Key
是字典中鍵的數據類型,Value
是字典中對應于這些鍵所存儲值的數據類型。
創建一個空字典
var namesOfIntegers = [Int: String]()
// namesOfIntegers 是一個空的 [Int: String] 字典
8.控制流
提前退出
像if
語句一樣,guard
的執行取決于一個表達式的布爾值。我們可以使用guard
語句來要求條件必須為真時,以執行guard
語句后的代碼。不同于if
語句,一個guard
語句總是有一個else
從句,如果條件不為真則執行else
從句中的代碼。
9.檢測 API 可用性
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
10.函數
參數標簽 (Specifying Argument Labels
)
可以在函數名稱前指定它的參數標簽,中間以空格分隔:
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
*注意:這里的from 就是參數hometown的標簽
參數標簽的使用能夠讓一個函數在調用時更有表達力,更類似自然語言,并且仍保持了函數內部的可讀性以及清晰的意圖。
忽略參數標簽(Omitting Argument Labels
)
如果不希望為某個參數添加一個標簽,可以使用一個下劃線(_
)來代替一個明確的參數標簽。
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
可變參數 (Variadic Parameters
)
一個可變參數(variadic parameter
)可以接受零個或多個值。通過在變量類型名后面加入(...
)的方式來定義可變參數
一個函數最多只能擁有一個可變參數。
輸入輸出參數(In-Out Parameters
)
函數參數默認是常量。
只能傳遞變量給輸入輸出參數。你不能傳入常量
或者字面量
(literal value
),因為這些量是不能被修改的。當傳入的參數作為輸入輸出參數時,需要在參數名前加 &
符,表示這個值可以被函數修改。
eg:
函數類型 (Function Types
)
每個函數都有種特定的函數類型,函數的類型由函數的參數類型
和返回類型
組成。
func eschop(_ a: Strinfg, _b: Int) -> String {
// func body
}
函數類型為:(String, Int) -> String
嵌套函數
可以把函數定義在別的函數體中,稱作 嵌套函數(nested functions
)
默認情況下,嵌套函數
是對外界不可見的,但是可以被它們的外圍函數
(enclosing function
)調用。
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!
11.閉包(Closures
)
閉包
是自包含
的函數代碼塊
,可以在代碼中被傳遞
和使用
。Swift
中的閉包
與 C
和 Objective-C
中的代碼塊(blocks
)以及其他一些編程語言中的匿名函數
比較相似。
閉包
可以捕獲和存儲其所在上下文
中任意常量
和變量
的引用。閉合
、包裹
常量和變量,所謂閉包
也。Swift
會為你管理在捕獲過程中涉及到的所有內存操作。
捕獲(capturing):閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。
Swift 中,可以捕獲值的閉包的最簡單形式是嵌套函數,也就是定義在其他函數的函數體內的函數。嵌套函數可以捕獲其外部函數所有的參數以及定義的常量和變量。
全局和嵌套函數實際上也是特殊的閉包:
閉包采取如下三種形式之一:
·全局函數是一個有名字但不會捕獲
任何值的閉包
·嵌套函數是一個有名字并可以捕獲
其封閉函數域內值的閉包
·閉包表達式是一個利用輕量級語法所寫的可以捕獲
其上下文中變量或常量值的匿名閉包
如果你將閉包
賦值給一個類實例的屬性,并且該閉包通過訪問該實例或其成員而捕獲
了該實例,你將在閉包和該實例間創建一個循環強引用。Swift 使用捕獲列表
來打破這種循環強引用。
Swift
的閉包表達式擁有簡潔的風格,并鼓勵在常見場景中進行語法優化,主要優化如下:
1)利用上下文推斷參數和返回值類型
2)隱式返回單表達式閉包,即單表達式閉包可以省略 return
關鍵字
3)參數名稱縮寫
4)尾隨(Trailing
)閉包語法
閉包表達式(Closure Expressions
)
嵌套函數是一個在較復雜函數中方便進行命名和定義自包含代碼模塊的方式。
當然,有時候編寫小巧的沒有完整定義和命名的類函數結構也是很有用處的,尤其是在你處理一些函數并需要將另外一些函數作為該函數的參數時。
閉包表達式
是一種利用簡潔語法構建內聯閉包的方式。
閉包表達式提供了一些語法優化,使得撰寫閉包變得簡單明了。
sorted(by:)
方法接受一個閉包,該閉包函數需要傳入與數組元素類型相同的兩個值,并返回一個布爾類型值來表明當排序結束后傳入的第一個參數排在第二個參數前面還是后面。如果第一個參數值出現在第二個參數值前面,排序閉包函數需要返回true
,反之返回false
。
利用閉包表達式語法可以更好地構造一個內聯排序閉包
。
閉包表達式語法(Closure Expression Syntax
)
閉包表達式語法有如下的一般形式:
閉包表達式的參數可以是inout
參數,但不能設定默認值。
也可以使用具名的可變參數.元組也可以作為參數和返回值。
內聯閉包參數和返回值類型聲明與 backward(::) 函數類型聲明相同。在內聯閉包表達式中,函數和返回值類型都寫在大括號內,而不是大括號外。
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
根據上下文推斷類型(Inferring Type From Context
)
通過內聯閉包表達式
構造的閉包
作為參數傳遞給函數或方法時,總是能夠推斷出閉包
的參數和返回值類型。
單表達式閉包隱式返回(Implicit Returns From Single-Expression Closures
)
單行表達式閉包可以通過省略 return 關鍵字來隱式返回單行表達式的結果,如上版本的例子可以改寫為:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
參數名稱縮寫(Shorthand Argument Names
)
reversedNames = names.sorted(by: { $0 > $1 } )
在這個例子中,$0和$1表示閉包中第一個和第二個 String 類型的參數。
運算符方法(Operator Methods
)
Swift
的 String
類型定義了關于大于號(>
)的字符串實現,其作為一個函數接受兩個 String
類型的參數并返回 Bool
類型的值。而這正好與 sorted(by:)
方法的參數需要的函數類型相符合。因此,你可以簡單地傳遞一個大于號,Swift
可以自動推斷出你想使用大于號的字符串函數實現:
reversedNames = names.sorted(by: >)
尾隨閉包(Trailing Closures
)
如果你需要將一個很長的閉包表達式
作為最后一個參數傳遞給函數,可以使用尾隨閉包
來增強函數的可讀性。
尾隨閉包是一個書寫在函數括號之后的閉包表達式,函數支持將其作為最后一個參數調用。在使用尾隨閉包時,你不用寫出它的參數標簽:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函數體部分
}
// 以下是不使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure(closure: {
// 閉包主體部分
})
// 以下是使用尾隨閉包進行函數調用
someFunctionThatTakesAClosure() {
// 閉包主體部分
}
reversedNames = names.sorted() { $0 > $1 } // 方法參數的字符串排序閉包可以改寫為:
reversedNames = names.sorted { $0 > $1 } // 如果閉包表達式是函數或方法的唯一參數,則當你使用尾隨閉包時,你甚至可以把 () 省略掉
當閉包非常長以至于不能在一行中進行書寫時,尾隨閉包變得非常有用。
舉例來說,Swift
的 Array
類型有一個 map(_:)
方法,這個方法獲取一個閉包表達式作為其唯一參數。該閉包函數會為數組中的每一個元素調用一次,并返回該元素所映射的值。具體的映射方式和返回值類型由閉包來指定。
閉包是引用類型(Closures Are Reference Types)
無論你將函數或閉包賦值給一個常量還是變量,你實際上都是將常量或變量的值設置為對應函數或閉包的引用。
逃逸閉包(Escaping Closures)
當一個閉包作為參數傳到一個函數中,但是這個閉包在函數返回之后才被執行,我們稱該閉包從函數中逃逸
。
當你定義接受閉包作為參數的函數時,你可以在參數名之前標注 @escaping
,用來指明這個閉包是允許“逃逸”出這個函數的。
一種能使閉包“逃逸”
出函數的方法是,將這個閉包保存在一個函數外部定義的變量中。舉個例子,很多啟動異步操作的函數接受一個閉包參數作為completion handler
。這類函數會在異步操作開始之后立刻返回,但是閉包直到異步操作結束后才會被調用。在這種情況下,閉包需要“逃逸”出函數,因為閉包需要在函數返回之后被調用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:)
函數接受一個閉包作為參數,該閉包被添加到一個函數外定義的數組中。如果你不將這個參數標記為 @escaping
,就會得到一個編譯錯誤。
自動閉包(Autoclosures)
自動閉包是一種自動創建的閉包,用于包裝傳遞給函數作為參數的表達式。這種閉包不接受任何參數,當它被調用的時候,會返回被包裝在其中的表達式的值。這種便利語法讓你能夠省略閉包的花括號,用一個普通的表達式來代替顯式的閉包。
12.枚舉
可以定義 Swift
枚舉來存儲任意類型的關聯值,如果需要的話,每個枚舉成員的關聯值類型可以各不相同。枚舉的這種特性跟其他語言中的可識別聯合(discriminated unions
),標簽聯合(tagged unions
),或者變體(variants
)相似。
原始值(Raw Values)
在關聯值小節的條形碼例子中,演示了如何聲明存儲不同類型關聯值的枚舉成員。作為關聯值的替代選擇,枚舉成員可以被默認值
(稱為原始值
)預填充,這些原始值的類型必須相同。
原始值的隱式賦值(Implicitly Assigned Raw Values)
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
當使用字符串作為枚舉類型的原始值時,每個枚舉成員的隱式原始值為該枚舉成員的名稱。
enum CompassPoint: String {
case north, south, east, west
}
上面例子中,CompassPoint.south
擁有隱式原始值south
,依次類推。
使用原始值初始化枚舉實例(Initializing from a Raw Value)
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 類型為 Planet? 值為 Planet.uranus
13.類和結構體
類和結構體是人們構建代碼所用的一種通用且靈活的構造體。我們可以使用完全相同的語法規則來為類和結構體定義屬性(常量、變量)和添加方法,從而擴展類和結構體的功能。
Swift
中類和結構體有很多共同點。共同處在于:
定義屬性用于存儲值
定義方法用于提供功能
定義下標操作使得可以通過下標語法來訪問實例所包含的值
定義構造器用于生成初始化值
通過擴展以增加默認實現的功能
實現協議以提供某種標準功能
每次定義一個新類或者結構體的時候,實際上你是定義了一個新的Swift
類型。
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
在以上示例中,聲明了一個名為hd
的常量,其值為一個初始化為全高清視頻分辨率(1920 像素寬,1080 像素高)的Resolution
實例。
然后示例中又聲明了一個名為cinema
的變量,并將hd賦值給它。因為Resolution
是一個結構體,所以cinema
的值其實是hd
的一個拷貝副本,而不是hd
本身。盡管hd
和cinema
有著相同的寬(width
)和高(height
),但是在幕后它們是兩個完全不同的實例。
類是引用類型
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
因為類是引用類型,所以tenEight
和alsoTenEight
實際上引用的是相同的VideoMode
實例。換句話說,它們是同一個實例的兩種叫法。
恒等運算符
因為類是引用類型,有可能有多個常量和變量在幕后同時引用同一個類實例。
如果能夠判定兩個常量或者變量是否引用同一個類實例將會很有幫助。為了達到這個目的,Swift
內建了兩個恒等運算符:
等價于(===
)
不等價于(!==
)
指針,使用指針來引用內存中的地址
一個引用某個引用類型實例的 Swift 常量或者變量,與 C 語言中的指針類似,但是并不直接指向某個內存地址,也不要求你使用星號(*)來表明你在創建一個引用。Swift 中的這些引用與其它的常量或變量的定義方式相同。
在所有其它案例中,定義一個類,生成一個它的實例,并通過引用來管理和傳遞。實際中,這意味著絕大部分的自定義數據構造都應該是類,而非結構體。
字符串(String)、數組(Array)、和字典(Dictionary)類型的賦值與復制行為
Swift
中,許多基本類型,諸如String
,Array
和Dictionary
類型均以結構體
的形式實現。這意味著被賦值給新的常量
或變量
,或者被傳入函數
或方法中時,它們的值會被拷貝
。
Objective-C
中NSString
,NSArray
和NSDictionary
類型均以類的形式實現,而并非結構體。它們在被賦值或者被傳入函數或方法時,不會發生值拷貝,而是傳遞現有實例的引用。
以上是對字符串、數組、字典的“拷貝”行為的描述。在你的代碼中,拷貝行為看起來似乎總會發生。然而,Swift
在幕后只在絕對必要時才執行實際的拷貝。Swift
管理所有的值拷貝以確保性能最優化,所以你沒必要去回避賦值來保證性能最優化。
14.屬性
屬性將值跟特定的類
、結構
或枚舉關聯
。存儲屬性存儲常量或變量作為實例的一部分,而計算屬性計算(不是存儲)一個值。計算屬性可以用于類、結構體和枚舉,存儲屬性只能用于類
和結構體
。
存儲屬性和計算屬性通常與特定類型的實例關聯。但是,屬性也可以直接作用于類型本身,這種屬性稱為類型屬性。
存儲屬性
常量結構體的存儲屬性
延遲存儲屬性
延遲存儲屬性是指當第一次被調用的時候才會計算其初始值的屬性。在屬性聲明前使用 lazy
來標示一個延遲存儲屬性。
存儲屬性和實例變量
如果您有過 Objective-C 經驗,應該知道 Objective-C 為類實例存儲值和引用提供兩種方法。除了屬性之外,還可以使用實例變量作為屬性值的后端存儲。
便捷 setter
聲明
全局變量和局部變量
計算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量。全局變量是在函數、方法、閉包或任何類型之外定義的變量。局部變量是在函數、方法或閉包內部定義的變量。
全局的常量或變量都是延遲計算的,跟延遲存儲屬性相似,不同的地方在于,全局的常量或變量不需要標記lazy
修飾符。
局部范圍的常量或變量從不延遲計算。
類型屬性
類型屬性語法
15.方法(Methods)
方法是與某些特定類型相關聯的函數。
類、結構體、枚舉都可以定義實例方法。
實例方法為給定類型的實例封裝了具體的任務與功能。
類、結構體、枚舉也可以定義類型方法;類型方法與類型本身相關聯。類型方法與 Objective-C
中的類方法(class methods
)相似。
修改方法的外部參數名稱(Modifying External Parameter Name Behavior for Methods)
有時為方法的第一個參數提供一個外部參數名稱是非常有用的,盡管這不是默認的行為。你自己可以為第一個參數添加一個顯式的外部名稱。
相反,如果你不想為方法的第二個及后續的參數提供一個外部名稱,可以通過使用下劃線(_
)作為該參數的顯式外部名稱,這樣做將覆蓋默認行為。
self 屬性
類型的每一個實例都有一個隱含屬性叫做self,self完全等同于該實例本身。你可以在一個實例的實例方法中使用這個隱含的self屬性來引用當前實例。
在實例方法中修改值類型
結構體和枚舉是值類型。默認情況下,值類型的屬性不能在它的實例方法中被修改。
類型方法 (Type Methods)
實例方法是被某個類型的實例調用的方法。你也可以定義在類型本身上調用的方法,這種方法就叫做類型方法(Type Methods)。在方法的func
關鍵字之前加上關鍵字static
,來指定類型方法。類還可以用關鍵字class
來允許子類重寫父類的方法實現。
在 Objective-C
中,你只能為 Objective-C
的類類型(classes
)定義類型方法(type-level methods
)。在 Swift
中,你可以為所有的類、結構體和枚舉定義類型方法。每一個類型方法都被它所支持的類型顯式包含
。
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
在類型方法的方法體(body)中,self
指向這個類型本身,而不是類型的某個實例。這意味著你可以用self
來消除類型屬性和類型方法參數之間的歧義(類似于我們在前面處理實例屬性和實例方法參數時做的那樣)。