Swift 介紹#
Swift 是一門開發 iOS, macOS, watchOS 和 tvOS 應用的新語言。
swift 是一種安全,快速和互動的程式語言。
swift 支持代碼預覽(playgrounds),這個特性可以允許程式員在不編譯和運行應用程序的前提下運行 Swift 代碼並實時查看結果。
Swift 透過採用現代程式模式來避免大量常見程式錯誤:
- 變量始終在使用前初始化。
- 檢查數組索引超出範圍的錯誤。
- 檢查整數是否溢出。
- 可選值確保明確處理 nil 值。
- 記憶體被自動管理。
- 錯誤處理允許從意外故障控制恢復。
基礎部分#
常量和變量#
聲明常量和變量, 常量和變量必須在使用前聲明,使用 let 來聲明常量,使用 var 來聲明變量。
示例:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
// 類型註解
var welcomeMessage: String
註釋#
單行註釋雙正斜杠(//), 多行註釋(/* 多行的 */)。Swift 的多行註釋可以嵌套在其它的多行註釋之中。
示例:
// 這是一個註釋
/* 這也是一個註釋,
但是是多行的 */
/* 這是第一個多行註釋的開頭
/* 這是第二個被嵌套的多行註釋 */
這是第一個多行註釋的結尾 */
分號#
Swift 並不強制要求你在每條語句的結尾處使用分號(;)。
同一行內寫多條獨立的語句必須用分號分隔。
let cat = "🐱"; print(cat)
// 輸出“🐱”
標識符#
標識符就是給變量、常量、方法、函數、枚舉、結構體、類、協議等指定的名字。構成標識符的字母均有一定的規範,Swift 語言中標識符的命名規則如下:
-
區分大小寫,Myname 與 myname 是兩個不同的標識符;
-
標識符首字符可以以下劃線(_)或者字母開始,但不能是數字;
-
標識符中其他字符可以是下劃線(_)、字母或數字。
例如: userName、User_Name、_sys_val、身高等為合法的標識符,而 2mail、room# 和 class 為非法的標識符。
注意中的字母採用的是 Unicode 編碼。Unicode 叫做統一編碼制,它包含了亞洲文字編碼,如中文、日文、韓文等字符,甚至是我們在聊天工具中使用的表情符號
如果一定要使用關鍵字作為標識符,可以在關鍵字前後添加重音符號(`),例如:
let `class` = "xiaobai"
關鍵字#
關鍵字是類似於標識符的保留字符序列,除非用重音符號(`)將其括起來,否則不能用作標識符。關鍵字是對編譯器具有特殊意義的預定義保留標識符。常見的關鍵字有以下 4 種。
與聲明有關的關鍵字
class deinit enum extension
func import init internal
let operator private protocol
public static struct subscript
typealias var
與語句有關的關鍵字
break case continue default
do else fallthrough for
if in return switch
where while
表達式和類型關鍵字
as dynamicType false is
nil self Self super
true _COLUMN_ _FILE_ _FUNCTION_
_LINE_
在特定上下文中使用的關鍵字
associativity convenience dynamic didSet
final get infix inout
lazy left mutating none
nonmutating optional override postfix
precedence prefix Protocol required
right set Type unowned
weak willSet
Swift 空格#
Swift 對空格的使用有一定的要求。
在 Swift 中,運算符不能直接跟在變量或常量的後面。例如下面的代碼會報錯:
let a= 1 + 2
錯誤信息是:
error: prefix/postfix '=' is reserved
意思大概是等號直接跟在前面或後面這種用法是保留的。
下面的代碼還是會報錯(繼續注意空格):
let a = 1+ 2
錯誤信息是:
error: consecutive statements on a line must be separated by ';'
這是因為 Swift 認為到 1 + 這個語句就結束了,2 就是下一個語句了。
只有這樣寫才不會報錯:
let a = 1 + 2; // 編碼規範推薦使用這種寫法
let b = 3+4 // 這樣也是OK的
整數、浮點數#
統一使用 Int 可以提高代碼的可復用性,避免不同類型數字之間的轉換, 並且匹配數字的類型推斷。
示例:
let minValue = UInt8.min // minValue 為 0,是 UInt8 類型
let maxValue = UInt8.max // maxValue 為 255,是 UInt8 類型
類型安全和類型推斷#
Swift 是一門類型安全的語言,這意味著 Swift 可以讓你清楚地知道值的類型。
如果你沒有顯式指定類型,Swift 會使用類型推斷來選擇合適的類型。(int、double)。
示例:
let meaningOfLife = 42
// meaningOfLife 會被推測為 Int 類型
let pi = 3.14159
// pi 會被推測為 Double 類型
數值型字面量、數值型類型轉換#
示例:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二進制的17
let octalInteger = 0o21 // 八進制的17
let hexadecimalInteger = 0x11 // 十六進制的17
類型別名#
類型別名(type aliases)就是給現有類型定義另一個名字。你可以使用 typealias 關鍵字來定義類型別名。
示例:
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 現在是 0
布爾值#
示例:
let orangesAreOrange = true
let turnipsAreDelicious = false
元組#
元組(tuples)把多個值組合成一個複合值。元組內的值可以是任意類型,並不要求是相同類型。
示例:
let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")
可選類型#
使用可選類型(optionals)來處理值可能缺失的情況。可選類型表示兩種可能:或者有值, 你可以解析可選類型訪問這個值, 或者根本沒有值。
示例:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一個可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現在不包含值
錯誤處理#
錯誤處理,應對程式執行中可能會遇到的錯誤條件。
示例:
func makeASandwich() throws {
// ...
}
do {
try makeASandwich()
eatASandwich()
} catch SandwichError.outOfCleanDishes {
washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
buyGroceries(ingredients)
}
斷言和先決條件#
斷言和先決條件,是在運行時所做的檢查。
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因為 age < 0,所以斷言會觸發
基本運算符#
Swift 支持大部分標準 C 語言的運算符,還提供了 C 語言沒有的區間運算符,例如 a..<b 或 a...b。
賦值運算符,算術運算符,組合賦值運算符,比較運算符,三元運算符,空合運算符,區間運算符,邏輯運算符
運算符分為一元、二元和三元運算符。
閉區間運算符(a...b)定義一個包含從 a 到 b(包括 a 和 b)的所有值的區間。
半開區間運算符(a..<b)定義一個從 a 到 b 但不包括 b 的區間。
閉區間操作符有另一個表達形式,可以表達往一側無限延伸的區間,(a...,...b)。
示例:
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 個人叫 \(names[i])")
}
// 第 1 個人叫 Anna
// 第 2 個人叫 Alex
// 第 3 個人叫 Brian
// 第 4 個人叫 Jack
字符串和字符#
字符串字面量,字符串插值,計算字符數量,訪問和修改字符串,子字符串,比較字符串
初始化空字符串,字符串可變性,字符串是值類型,連接字符串和字符 (+,+=)。
使用字符,可通過 for-in 循環來遍歷字符串,獲取字符串中每一個字符的值。
字符串插值是一種構建新字符串的方式,可以在其中包含常量、變量、字面量和表達式。可以在已有字符串中插入常量、變量、字面量和表達式從而形成更長的字符串。
Swift 提供了三種方式來比較文本值:字符串字符相等、前綴相等和後綴相等。
示例:
// 多行字符串字面量
let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""
// 下面兩個字符串其實是相同的
let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""
// 字符串插值
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message 是 "3 times 2.5 is 7.5"
// 計算字符數量
var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// 打印輸出“the number of characters in cafe is 4”
var emptyString = "" // 空字符串字面量
var anotherEmptyString = String() // 初始化方法
// 兩個字符串均為空並等價。
let catCharacters: [Character] = ["C", "a", "t", "!"]
let catString = String(catCharacters)
print(catString)
// 打印輸出:“Cat!”
集合類型#
Swift 語言提供數組(Array)、集合(Set)和字典(Dictionary)三種基本的集合類型用來存儲集合數據。數組是有序數據的集。集合是無序無重複數據的集。字典是無序的鍵值對的集。
集合的可變性,數組(Arrays),集合(Sets),集合操作,字典
數組使用有序列表存儲同一類型的多個值。相同的值可以多次出現在一個數組的不同位置中。
集合用來存儲相同類型並且沒有確定順序的值。當集合元素順序不重要時或者希望確保每個元素只出現一次時可以使用集合而不是數組。
集合操作,可以高效地完成集合的一些基本操作,比如把兩個集合組合到一起,判斷兩個集合共有元素,或者判斷兩個集合是否全包含,部分包含或者不相交。
字典是一種無序的集合,它存儲的是鍵值對之間的關係,其所有鍵的值需要是相同的類型,所有值的類型也需要相同。每個值(value)都關聯唯一的鍵(key),鍵作為字典中這個值數據的標識符。
示例:
// 集合
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 打印“someInts is of type [Int] with 0 items。”
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles 是一種 [Double] 數組,等價於 [0.0, 0.0, 0.0]
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles 被推斷為 [Double],等價於 [2.5, 2.5, 2.5]
var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles 被推斷為 [Double],等價於 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
// enumerated() 方法遍歷數組
var shoppingList: [String] = ["Eggs", "Milk"]
for (index, value) in shoppingList.enumerated() {
print("Item \(String(index + 1)): \(value)")
}
控制流#
For-In 循環,While 循環(Repeat-While),條件語句,控制轉移語句,提前退出(guard),檢測 API 可用性
像 if 語句一樣,guard 的執行取決於一個表達式的布爾值。我們可以使用 guard 語句來要求條件必須為真時,以執行 guard 語句後的代碼。不同於 if 語句,一個 guard 語句總是有一個 else 從句,如果條件不為真則執行 else 從句中的代碼。
Swift 內置支持檢查 API 可用性,編譯器使用 SDK 中的可用信息來驗證我們的代碼中使用的所有 API 在項目指定的部署目標上是否可用。如果我們嘗試使用一個不可用的 API,Swift 會在編譯時報錯。
示例:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// repeat-while 循環的一般格式
repeat {
statements
} while condition
// 提前退出
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(person: ["name": "John"])
// 輸出“Hello John!”
// 輸出“I hope the weather is nice near you.”
greet(person: ["name": "Jane", "location": "Cupertino"])
// 輸出“Hello Jane!”
// 輸出“I hope the weather is nice in Cupertino。”
函數#
函數的定義與調用,函數參數與返回值,函數參數標籤和參數名稱,函數類型,嵌套函數
可選元組返回類型。
定義一個輸入輸出參數時,在參數定義前加 inout 關鍵字。
示例:
// 函數
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印“Hello Bill! Glad you could visit from Cupertino。”
// 可選元組返回類型
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
// 隱式返回的函數
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// 打印 "Hello, Dave!"
// 參數標籤
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// 打印“Hello Bill! Glad you could visit from Cupertino。”
閉包#
閉包是自包含的函數代碼塊,可以在代碼中被傳遞和使用。與一些程式語言中的匿名函數(Lambdas)比較相似。
閉包表達式,尾隨閉包,值捕獲,閉包是引用類型,逃逸閉包(@escaping),自動閉包
如果你需要將一個很長的閉包表達式作為最後一個參數傳遞給函數,將這個閉包替換成為尾隨閉包的形式很有用。
閉包可以在其被定義的上下文中捕獲常量或變量。即使定義這些常量和變量的原作用域已經不存在,閉包仍然可以在閉包函數體內引用和修改這些值。
示例:
// 閉包表達式語法
{ (parameters) -> return type in
statements
}
// 尾隨閉包
let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
(number) -> String in
var number = number
var output = ""
repeat {
output = digitNames[number % 10]! + output
number /= 10
} while number > 0
return output
}
// strings 常量被推斷為字符串類型數組,即 [String]
// 其值為 ["OneSix", "FiveEight", "FiveOneZero"]
// 值捕獲
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
// 自動閉包,延遲求值
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出“5”
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出“5”
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出“4”
枚舉#
使用 enum 關鍵字來創建枚舉並且把它們的整個定義放在一對大括號內。
枚舉語法,使用 Switch 語句匹配枚舉值,枚舉成員的遍歷,關聯值,原始值(默認值),遞歸枚舉(indirect)
可以定義 Swift 枚舉來存儲任意類型的關聯值,每個枚舉成員的關聯值類型可以各不相同。
示例:
// 枚舉語法
enum SomeEnumeration {
// 枚舉定義放在這裡
}
enum CompassPoint {
case north
case south
case east
case west
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 打印“Mostly harmless”
// 關聯值
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 打印“QR code: ABCDEFGHIJKLMNOP.”
// 遞歸枚舉
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))
// (5 + 4) * 2
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// 打印“18”
結構體和類#
結構體和類對比,結構體和枚舉是值類型,類是引用類型
結構體和類作為一種通用而又靈活的結構,成為了人們構建代碼的基礎。你可以使用定義常量、變量和函數的語法,為你的結構體和類定義屬性、添加方法。
示例:
// 類和結構體
struct SomeStructure {
// 在這裡定義結構體
}
class SomeClass {
// 在這裡定義類
}
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
屬性#
存儲屬性,計算屬性,屬性觀察器,屬性包裝器,全局變量和局部變量,類型屬性(static)
屬性將值與特定的類、結構體或枚舉關聯。存儲屬性會將常量和變量存儲為實例的一部分,而計算屬性則是直接計算(而不是存儲)值。計算屬性可以用於類、結構體和枚舉,而存儲屬性只能用於類和結構體。
屬性觀察器監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,即使新值和當前值相同的時候也不例外。
- willSet 在新的值被設置之前調用
- didSet 在新的值被設置之後調用
屬性包裝器在管理屬性如何存儲和定義屬性的代碼之間添加了一個分隔層。
類型屬性也是通過點運算符來訪問。但是,類型屬性是通過類型本身來訪問,而不是通過實例。
示例:
// 屬性
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
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)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”
// 屬性包裝器
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
方法#
實例方法(Instance Methods),類型方法(static)
方法是與某些特定類型相關聯的函數。
類、結構體、枚舉都可以定義實例方法;實例方法為給定類型的實例封裝了具體的任務與功能。
類、結構體、枚舉也可以定義類型方法;類型方法與類型本身相關聯。
示例:
// 方法
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
下標#
下標可以定義在類、結構體和枚舉中,是訪問集合、列表或序列中元素的快捷方式
下標語法(subscript),下標用法,下標選項,類型下標(static)
subscript(index: Int) -> Int {
get {
// 返回一個適當的 Int 類型的值
}
set(newValue) {
// 執行適當的賦值操作
}
}
// 示例
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 打印“six times three is 18”
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
// 類型下標
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
繼承#
定義一個基類,子類生成,重寫 (override),防止重寫 (final)
不繼承於其它類的類,稱之為基類。
示例:
// 繼承
class SomeClass: SomeSuperclass {
// 這裡是子類的定義
}
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什麼也不做——因為車輛不一定會有噪音
}
}
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
構造過程#
構造過程是使用類、結構體或枚舉類型的實例之前的準備過程。
存儲屬性的初始賦值,自定義構造過程,默認構造器,值類型的構造器代理,類的繼承和構造過程,可失敗構造器,必要構造器(required)
構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理,它能避免多個構造器間的代碼重複。
Swift 為類類型提供了兩種構造器來確保實例中所有存儲型屬性都能獲得初始值,它們被稱為指定構造器和便利構造器。
可以在一個類,結構體或是枚舉類型的定義中,添加一個或多個可失敗構造器。其語法為在 init 關鍵字後面添加問號(init?)。
必要構造器,在類的構造器前添加 required 修飾符表明所有該類的子類都必須實現該構造器。
示例:
// 構造過程
init() {
// 在此處執行構造過程
}
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印“The default temperature is 32.0° Fahrenheit”
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
析構過程#
析構器只適用於類類型,當一個類的實例被釋放之前,析構器會被立即調用。析構器用關鍵字 deinit 來標示,類似於構造器要用 init 來標示。
Swift 會自動釋放不再需要的實例以釋放資源。
示例:
// 析構過程
deinit {
// 執行析構過程
}
class Bank {
static var coinsInBank = 10_000
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receive(coins: Int) {
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.distribute(coins: coins)
}
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
deinit {
Bank.receive(coins: coinsInPurse)
}
}
可選鏈式調用#
可選鏈式調用是一種可以在目前值可能為 nil 的可選值上請求和調用屬性、方法及下標的方法。
通過在想調用的屬性、方法,或下標的可選值後面放一個問號(?),可以定義一個可選鏈。類似在可選值後面放一個驚嘆號(!)來強制展開它的值。它們的主要區別在於當可選值為空時可選鏈式調用只會調用失敗,然而強制展開將會觸發運行時錯誤。
示例:
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
let roomCount = john.residence!.numberOfRooms
// 這會引發運行時錯誤
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印“Unable to retrieve the number of rooms。”
john.residence = Residence()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印“John's residence has 1 room(s).”
錯誤處理#
錯誤處理(Error handling) 是響應錯誤以及從錯誤中恢復的過程。Swift 在運行時提供了拋出、捕獲、傳遞和操作可恢復錯誤(recoverable errors)的一等支持。
表示與拋出錯誤,處理錯誤,指定清理操作
在 Swift 中,錯誤用遵循 Error 協議的類型的值來表示。
Swift 中有 4 種處理錯誤的方式。可以把函數拋出的錯誤傳遞給調用此函數的代碼(throws)、用 do-catch 語句處理錯誤、將錯誤作為可選類型處理(try?)、或者斷言此錯誤根本不會發生(try!)。
defer 語句將代碼的執行延遲到當前的作用域退出之前。
示例:
// 錯誤處理
enum VendingMachineError: Error {
case invalidSelection //選擇無效
case insufficientFunds(coinsNeeded: Int) //金額不足
case outOfStock //缺貨
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
}
// 打印“Insufficient funds. Please insert an additional 2 coins。”
// 指定清理操作
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 操作符實現。這兩個操作符分別提供了一種簡單達意的方式去檢查值的類型或者轉換它的類型。
為類型轉換定義類層次,檢查類型(is),向下轉型(as? 或 as!),Any 和 AnyObject 的類型轉換
可以將類型轉換用在類和子類的層次結構上,檢查特定類實例的類型並且轉換這個類實例的類型成為這個層次結構中的其他類型。
Swift 為不確定類型提供了兩種特殊的類型別名:
-
Any 可以表示任何類型,包括函數類型。
-
AnyObject 可以表示任何類類型的實例。
示例:
// 類型轉換
// 一個基類 MediaItem
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.srtist = artist
super.init(name: name)
}
}
let library = [
Movie(name: "Casablanca", director: "Micheal Curtiz"),
Song(name: "Blue Suede Shose", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Wells"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
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)")
// 打印“Media library contains 2 movies and 3 songs”
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
嵌套類型#
Swift 允許定義嵌套類型,可以在支持的類型中定義嵌套的枚舉、類和結構體。
嵌套類型實踐,引用嵌套類型
要在一個類型中嵌套另一個類型,將嵌套類型的定義寫在其外部類型的 {} 內,而且可以根據需要定義多級嵌套。
示例:
// 嵌套類型
stuct BlackjackCard {
// 嵌套的 Suit 枚舉
enum Suit: Character {
case spades = "1", hearts = "2", diamonds = "3", clubs = "4"
}
// 嵌套的 Rank 枚舉
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
// BlackjackCard 的屬性和方法
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 打印“theAceOfSpades: suit is 1, value is 1 or 11”
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// 2
擴展#
擴展可以給一個現有的類,結構體,枚舉,還有協議添加新的功能。
擴展的語法,計算型屬性,構造器,方法,下標,嵌套類型
Swift 中的擴展可以:
-
添加計算型實例屬性和計算型類屬性
-
定義實例方法和類方法
-
提供新的構造器
-
定義下標
-
定義和使用新的嵌套類型
-
使已經存在的類型遵循(conform)一個協議
擴展語法:
extension SomeType {
// 在這裡給 SomeType 添加新的功能
}
擴展可以給現有類型添加計算型實例屬性和計算型類屬性。
擴展可以給現有的類型添加新的構造器。
擴展可以給現有類型添加新的實例方法和類方法。
擴展可以給現有類型添加新的下標。
擴展可以給現有的類,結構體,還有枚舉添加新的嵌套類型。
示例:
// 擴展的語法
extension SomeType {
// 在這裡給 SomeType 添加新的功能
}
// 添加一個或多個協議
extension SomeType: SomeProtocol, AnotherProtocol {
// 協議所需要的實現寫在這裡
}
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 3)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5) 並且它的 size 是 (3.0, 3.0)
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
extension Int {
mutating func square() {
self = self * self
}
}
var somtInt = 3
someInt.square()
// someInt 現在是9
協議#
協議定義了一個藍圖,規定了用來實現某一特定任務或者功能的方法、屬性,以及其他需要的東西。
類、結構體或枚舉都可以遵循協議,並為協議定義的這些要求提供具體實現。
協議語法,屬性要求,方法要求,異變方法要求,構造器要求,協議作為類型,委託,協議類型的集合,協議的繼承,類專屬的協議,協議合成,檢查協議一致性,可選的協議要求,協議擴展,
協議語法
protocol SomeProtocol {
// 這裡是協議的定義部分
}
協議可以要求遵循協議的類型提供特定名稱和類型的實例屬性或類型屬性。
協議可以要求遵循協議的類型實現某些指定的實例方法或類方法。
在值類型(即結構體和枚舉)的實例方法中,將 mutating 關鍵字作為方法的前綴,寫在 func 關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值。
協議可以要求遵循協議的類型實現指定的構造器。
委託是一種設計模式,它允許類或結構將一些需要它們負責的功能委託給其他類型的實例。
示例:
// 協議語法
protocol SomeProtocol {
// 這裡是協議的定義部分
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這裡是結構體的定義部分
}
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這裡是類的定義部分
}
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
struct person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed"
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName 為 "USS Enterprise"
泛型#
泛型代碼讓你能根據自定義的需求,編寫出適用於任意類型的、靈活可重用的函數及類型。
你可避免編寫重複的代碼,而是用一種清晰抽象的方式來表達代碼的意圖。
泛型函數,類型參數,命名類型參數,泛型類型,泛型擴展,類型約束,關聯類型
示例:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 現在是 107,anotherInt 現在是 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 現在是“world”,anotherString 現在是“hello”
不透明類型#
具有不透明返回類型的函數或方法會隱藏返回值的類型信息。
函數不再提供具體的類型作為返回類型,而是根據它支持的協議來描述返回值。
不透明類型解決的問題,返回不透明類型,不透明類型和協議類型的區別
在處理模塊和調用代碼之間的關係時,隱藏類型信息非常有用,因為返回的底層數據類型仍然可以保持私有。
不透明類型和泛型相反。不透明類型允許函數實現時,選擇一個與調用代碼無關的返回類型。
如果函數中有多個地方返回了不透明類型,那麼所有可能的返回值都必須是同一類型。返回不透明類型和返回協議類型主要區別,就在於是否需要保證類型一致性。
一個不透明類型只能對應一個具體的類型,即便函數調用者並不能知道是哪一種類型;協議類型可以同時對應多個類型,只要它們都遵循同一協議。
示例:
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result = [String]()
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
自動引用計數#
Swift 使用自動引用計數(ARC)機制來跟蹤和管理你的應用程序的內存。
如果兩個類實例互相持有對方的強引用,因而每個實例都讓對方一直存在,就是這種情況。這就是所謂的循環強引用。
Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:弱引用(weak reference)和無主引用(unowned reference)。
-
聲明屬性或者變量時,在前面加上 weak 關鍵字表明這是一個弱引用。
-
聲明屬性或者變量時,在前面加上關鍵字 unowned 表示這是一個無主引用。
示例:
// 自動引用計數實踐
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed")
// 打印“John Appleseed is being initialized”
reference2 = reference1
reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
// 打印“John Appleseed is being deinitialized”
// 循環強引用
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john = nil
unit4A = nil
// 弱引用
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil
// 打印“John Appleseed is being deinitialized”
內存安全#
默認情況下,Swift 會阻止你代碼裡不安全的行為。
理解內存訪問衝突,In-Out 參數的訪問衝突,方法裡 self 的訪問衝突,屬性的訪問衝突
示例:
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // 正常
balance(&playerOneScore, &playerOneScore)
// 錯誤:playerOneScore 訪問衝突
訪問控制#
訪問控制可以限定其它源文件或模塊對你的代碼的訪問。
-
open 和 public 級別可以讓實體被同一模塊源文件中的所有實體訪問,在模塊外也可以通過導入該模塊來訪問源文件裡的所有實體。通常情況下,你會使用 open 或 public 級別來指定框架的外部接口。
-
internal 級別讓實體被同一模塊源文件中的任何實體訪問,但是不能被模塊外的實體訪問。通常情況下,如果某個接口只在應用程序或框架內部使用,就可以將其設置為 internal 級別。
-
fileprivate 限制實體只能在其定義的文件內部訪問。如果功能的部分實現細節只需要在文件內使用時,可以使用 fileprivate 來將其隱藏。
-
private 限制實體只能在其定義的作用域,以及同一文件內的 extension 訪問。如果功能的部分細節只需要在當前作用域內使用時,可以使用 private 來將其隱藏。
open 為最高訪問級別(限制最少),private 為最低訪問級別(限制最多)。
open 只能作用於類和類的成員,它和 public 的區別主要在於 open 限定的類和成員能夠在模塊外能被繼承和重寫。
示例:
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
class SomeInternalClass {} // 隱式 internal
var someInternalConstant = 0 // 隱式 internal
public class SomePublicClass { // 顯式 public 類
public var somePublicProperty = 0 // 顯式 public 類成員
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
class SomeInternalClass { // 隱式 internal 類
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
fileprivate class SomeFilePrivateClass { // 顯式 fileprivate 類
func someFilePrivateMethod() {} // 隱式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
private class SomePrivateClass { // 顯式 private 類
func somePrivateMethod() {} // 隱式 private 類成員
}
高級運算符#
Swift 還提供了數種可以對數值進行複雜運算的高級運算符。它們包含了位運算符和移位運算符。
位運算符、溢出運算符、優先級和結合性、運算符函數、自定義運算符
示例:
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等於 0b11110000
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767,這是 Int16 能容納的最大整數
potentialOverflow += 1
// 這裡會報錯
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一個新的 Vector2D 實例,值為 (5.0, 5.0)