這里的教程為Swift官方教程中文版。

表達式(Expressions)

Swift 中存在四種表達式:前綴表達式,二元表達式,基本表達式和后綴表達式。表達式在返回一個值的同時還可以引發(fā)副作用。

通過前綴表達式和二元表達式可以對簡單表達式使用各種運算符。基本表達式從概念上講是最簡單的一種表達式,它是一種訪問值的方式。后綴表達式則允許你建立復雜的表達式,例如函數(shù)調(diào)用和成員訪問。每種表達式都在下面有詳細論述。

表達式語法

expression

表達式try 運算符可選 前綴表達式 二元表達式列表可選

expression-list

表達式列表表達式 | 表達式 , 表達式列表

前綴表達式

前綴表達式由可選的前綴運算符和表達式組成。前綴運算符只接收一個參數(shù),表達式則緊隨其后。

關(guān)于這些運算符的更多信息,請參閱 基本運算符高級運算符

關(guān)于 Swift 標準庫提供的運算符的更多信息,請參閱 Operators Declarations

除了標準庫運算符,你也可以對某個變量使用 & 運算符,從而將其傳遞給函數(shù)的輸入輸出參數(shù)。更多信息,請參閱 輸入輸出參數(shù)

前綴表達式語法

prefix-expression

前綴表達式前綴運算符可選 后綴表達式

前綴表達式輸入輸出表達式

in-out-expression

輸入輸出表達式& 標識符

Try 運算符

try 表達式由 try 運算符加上緊隨其后的可拋出錯誤的表達式組成,形式如下:

try 可拋出錯誤的表達式

可選的 try 表達式由 try? 運算符加上緊隨其后的可拋出錯誤的表達式組成,形式如下:

try? 可拋出錯誤的表達式

如果可拋出錯誤的表達式?jīng)]有拋出錯誤,整個表達式返回的可選值將包含可拋出錯誤的表達式的返回值,否則,該可選值為 nil

強制的 try 表達式由 try! 運算符加上緊隨其后的可拋出錯誤的表達式組成,形式如下:

try! 可拋出錯誤的表達式

如果可拋出錯誤的表達式拋出了錯誤,將會引發(fā)運行時錯誤。

在二元運算符左側(cè)的表達式被標記上 try、try? 或者 try! 時,這個運算符對整個二元表達式都產(chǎn)生作用。也就是說,你可以使用括號來明確運算符的作用范圍。

sum = try someThrowingFunction() + anotherThrowingFunction()   // try 對兩個函數(shù)調(diào)用都產(chǎn)生作用
sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 對兩個函數(shù)調(diào)用都產(chǎn)生作用
sum = (try someThrowingFunction()) + anotherThrowingFunction() // 錯誤:try 只對第一個函數(shù)調(diào)用產(chǎn)生作用

try 表達式不能出現(xiàn)在二元運算符的的右側(cè),除非二元運算符是賦值運算符或者 try 表達式是被圓括號括起來的。

關(guān)于 trytry?try! 的更多信息,以及該如何使用的例子,請參閱 錯誤處理。

Try 表達式語法

try-operator

try 運算符try | try? | try!

二元表達式

二元表達式由中綴運算符和左右參數(shù)表達式組成。形式如下:

左側(cè)參數(shù) 二元運算符 右側(cè)參數(shù)

關(guān)于這些運算符的更多信息,請參閱 基本運算符高級運算符

關(guān)于 Swift 標準庫提供的運算符的更多信息,請參閱 Swift Standard Library Operators Reference。

注意

在解析時,一個二元表達式將作為一個扁平列表表示,然后根據(jù)運算符的優(yōu)先級,再進一步進行組合。例如,2 + 3 * 5 首先被看作具有五個元素的列表,即 2+、3*、5,隨后根據(jù)運算符優(yōu)先級組合為 (2 + (3 * 5))

binary-expression

二元表達式語法

二元表達式二元運算符 前綴表達式

二元表達式賦值運算符 try 運算符可選 前綴表達式

二元表達式條件運算符 try 運算符可選 前綴表達式

二元表達式類型轉(zhuǎn)換運算符

binary-expressions

二元表達式列表二元表達式 二元表達式列表可選

賦值表達式

賦值表達式會為某個給定的表達式賦值,形式如下;

表達式 =

右邊的值會被賦值給左邊的表達式。如果左邊表達式是一個元組,那么右邊必須是一個具有同樣元素個數(shù)的元組。(嵌套元組也是允許的。)右邊的值中的每一部分都會被賦值給左邊的表達式中的相應部分。例如:

(a, _, (b, c)) = ("test", 9.45, (12, 3))
// a 為 "test",b 為 12,c 為 3,9.45 會被忽略

賦值運算符不返回任何值。

賦值運算符語法

assignment-operator

賦值運算符=

三元條件運算符

三元條件運算符會根據(jù)條件來對兩個給定表達式中的一個進行求值,形式如下:

條件 ? 表達式(條件為真則使用) : 表達式(條件為假則使用)

如果條件為真,那么對第一個表達式進行求值并返回結(jié)果。否則,對第二個表達式進行求值并返回結(jié)果。未使用的表達式不會進行求值。

關(guān)于使用三元條件運算符的例子,請參閱 三元條件運算符

三元條件運算符語法

conditional-operator

三元條件運算符? 表達式 :

類型轉(zhuǎn)換運算符

有 4 種類型轉(zhuǎn)換運算符:is、as、as?as!。它們有如下的形式:

表達式 is 類型

表達式 as 類型

表達式 as? 類型

表達式 as! 類型

is 運算符在運行時檢查表達式能否向下轉(zhuǎn)化為指定的類型,如果可以則返回 ture,否則返回 false

as 運算符在編譯時執(zhí)行向上轉(zhuǎn)換和橋接。向上轉(zhuǎn)換可將表達式轉(zhuǎn)換成父類的實例而無需使用任何中間變量。以下表達式是等價的:

func f(any: Any) { print("Function for Any") }
func f(int: Int) { print("Function for Int") }
let x = 10
f(x)
// 打印“Function for Int”

let y: Any = x
f(y)
// 打印“Function for Any”

f(x as Any)
// 打印“Function for Any”

橋接可將 Swift 標準庫中的類型(例如 String)作為一個與之相關(guān)的 Foundation 類型(例如 NSString)來使用,而不需要新建一個實例。關(guān)于橋接的更多信息,請參閱 Working with Foundation Types

as? 運算符有條件地執(zhí)行類型轉(zhuǎn)換,返回目標類型的可選值。在運行時,如果轉(zhuǎn)換成功,返回的可選值將包含轉(zhuǎn)換后的值,否則返回 nil。如果在編譯時就能確定轉(zhuǎn)換一定會成功或是失敗,則會導致編譯報錯。

as! 運算符執(zhí)行強制類型轉(zhuǎn)換,返回目標類型的非可選值。如果轉(zhuǎn)換失敗,則會導致運行時錯誤。表達式 x as! T 效果等同于 (x as? T)!。

關(guān)于類型轉(zhuǎn)換的更多內(nèi)容和例子,請參閱 類型轉(zhuǎn)換

type-casting-operator

類型轉(zhuǎn)換運算符語法

類型轉(zhuǎn)換運算符is 類型

類型轉(zhuǎn)換運算符as 類型

類型轉(zhuǎn)換運算符as ? 類型

類型轉(zhuǎn)換運算符as ! 類型

基本表達式

基本表達式是最基本的表達式。它們可以單獨使用,也可以跟前綴表達式、二元表達式、后綴表達式組合使用。

基本表達式語法

primary-expression

基本表達式標識符 泛型實參子句可選

基本表達式字面量表達式

基本表達式self 表達式

基本表達式父類表達式

基本表達式閉包表達式

基本表達式圓括號表達式

基本表達式隱式成員表達式

基本表達式通配符表達式

基本表達式選擇器表達式

基本表達式key-path字符串表達式

字面量表達式

字面量表達式可由普通字面量(例如字符串或者數(shù)字),字典或者數(shù)組字面量,或者下面列表中的特殊字面量組成:

字面量 類型
#file String 所在的文件名
#line Int 所在的行數(shù)
#column Int 所在的列數(shù)
#function String 所在的聲明的名字

對于 #function,在函數(shù)中會返回當前函數(shù)的名字,在方法中會返回當前方法的名字,在屬性的存取器中會返回屬性的名字,在特殊的成員如 initsubscript 中會返回這個關(guān)鍵字的名字,在某個文件中會返回當前模塊的名字。

當其作為函數(shù)或者方法的默認參數(shù)值時,該字面量的值取決于函數(shù)或方法的調(diào)用環(huán)境。

func logFunctionName(string: String = #function) {
    print(string)
}
func myFunction() {
    logFunctionName()
}
myFunction() // 打印“myFunction()”

數(shù)組字面量是值的有序集合,形式如下:

[值 1, 值 2, ...]

數(shù)組中的最后一個表達式可以緊跟一個逗號。數(shù)組字面量的類型是 [T],這個 T 就是數(shù)組中元素的類型。如果數(shù)組中包含多種類型,T 則是跟這些類型最近的的公共父類型??諗?shù)組字面量由一組方括號定義,可用來創(chuàng)建特定類型的空數(shù)組。

var emptyArray: [Double] = []

字典字面量是一個包含無序鍵值對的集合,形式如下:

[鍵 1 : 值 1, 鍵 2 : 值 2, ...]

字典中的最后一個表達式可以緊跟一個逗號。字典字面量的類型是 [Key : Value],Key 表示鍵的類型,Value 表示值的類型。如果字典中包含多種類型,那么 Key 表示的類型則為所有鍵最接近的公共父類型,Value 與之相似。一個空的字典字面量由方括號中加一個冒號組成([:]),從而與空數(shù)組字面量區(qū)分開,可以使用空字典字面量來創(chuàng)建特定類型的字典。

var emptyDictionary: [String : Double] = [:]

Xcode 使用 playground 字面量對程序編輯器中的顏色、文件或者圖片創(chuàng)建可交互的展示。在 Xcode 之外的空白文本中,playground 字面量使用一種特殊的字面量語法來展示。

更多關(guān)于在 Xcode 中使用 playground 字面量的信息,請參閱 添加顏色、文件或圖片字面量。

字面量表達式語法

literal-expression

字面量表達式字面量

字面量表達式數(shù)組字面量 | 字典字面量 | 練習場字面量

字面量表達式#file | #line | #column | #function

array-literal

數(shù)組字面量 → [數(shù)組字面量項列表可選 ]

array-literal-items

數(shù)組字面量項列表數(shù)組字面量項 ,可選 | 數(shù)組字面量項 , 數(shù)組字面量項列表

array-literal-item

數(shù)組字面量項表達式

dictionary-literal

字典字面量 → [字典字面量項列表 ] | [ : ]

dictionary-literal-items

字典字面量項列表字典字面量項 ,可選 | 字典字面量項 , 字典字面量項列表

dictionary-literal-item

字典字面量項表達式 : 表達式

playground-literal

playground 字面量#colorLiteral ( red : 表達式 , green :表達式 表達式 xpression) , blue :表達式 , alpha : 表達式 )

playground 字面量#fileLiteral ( resourceName : 表達式 )

playground 字面量*#imageLiteral ( resourceName : 表達式 )self-expression

Self 表達式

self 表達式是對當前類型或者當前實例的顯式引用,它有如下形式:

self

self.成員名稱

self[下標索引]

self(構(gòu)造器參數(shù))

self.init(構(gòu)造器參數(shù))

如果在構(gòu)造器、下標、實例方法中,self 引用的是當前類型的實例。在一個類型方法中,self 引用的是當前的類型。

當訪問成員時,self 可用來區(qū)分重名變量,例如函數(shù)的參數(shù):

class SomeClass {
    var greeting: String
    init(greeting: String) {
        self.greeting = greeting
    }
}

mutating 方法中,你可以對 self 重新賦值:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveByX(deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

Self 表達式語法

self-expression

self 表達式self | self 方法表達式self 下標表達式 | self 構(gòu)造器表達式

self-method-expression

self 方法表達式self . 標識符

self-subscript-expression

self 下標表達式self [ 函數(shù)調(diào)用參數(shù)表 ]

self-initializer-expression

self 構(gòu)造器表達式self . init

父類表達式

父類表達式可以使我們在某個類中訪問它的父類,它有如下形式:

super.成員名稱

super[下標索引]

super.init(構(gòu)造器參數(shù))

第一種形式用來訪問父類的某個成員,第二種形式用來訪問父類的下標,第三種形式用來訪問父類的構(gòu)造器。

子類可以通過父類表達式在它們的成員、下標和構(gòu)造器中使用父類中的實現(xiàn)。

父類表達式語法

superclass-expression

父類表達式父類方法表達式 | 父類下標表達式 | 父類構(gòu)造器表達式

superclass-method-expression

父類方法表達式super . 標識符

superclass-subscript-expression

父類下標表達式super [函數(shù)調(diào)用參數(shù)表 ]

superclass-initializer-expression

父類構(gòu)造器表達式super . init

閉包表達式

閉包表達式會創(chuàng)建一個閉包,在其他語言中也叫 lambda匿名函數(shù)。跟函數(shù)一樣,閉包包含了待執(zhí)行的代碼,不同的是閉包還會捕獲所在環(huán)境中的常量和變量。它的形式如下:

{ (parameters) -> return type in
    statements
}

閉包的參數(shù)聲明形式跟函數(shù)一樣,請參閱 函數(shù)聲明

閉包還有幾種特殊的形式,能讓閉包使用起來更加簡潔:

  • 閉包可以省略它的參數(shù)和返回值的類型。如果省略了參數(shù)名和所有的類型,也要省略 in 關(guān)鍵字。如果被省略的類型無法被編譯器推斷,那么就會導致編譯錯誤。
  • 閉包可以省略參數(shù)名,參數(shù)會被隱式命名為 $ 加上其索引位置,例如 $0、$1$2 分別表示第一個、第二個、第三個參數(shù),以此類推。
  • 如果閉包中只包含一個表達式,那么該表達式的結(jié)果就會被視為閉包的返回值。表達式結(jié)果的類型也會被推斷為閉包的返回類型。

下面幾個閉包表達式是等價的:

myFunction {
    (x: Int, y: Int) -> Int in
    return x + y
}

myFunction {
    (x, y) in
    return x + y
}

myFunction { return $0 + $1 }

myFunction { $0 + $1 }

關(guān)于如何將閉包作為參數(shù)來傳遞的內(nèi)容,請參閱 函數(shù)調(diào)用表達式。

使用閉包表達式時,可以不必將其存儲在一個變量或常量中,例如作為函數(shù)調(diào)用的一部分來立即使用一個閉包。在上面的例子中,傳入 myFunction 的閉包表達式就是這種立即使用類型的閉包。因此,一個閉包是否逃逸與其使用時的上下文相關(guān)。一個會被立即調(diào)用或者作為函數(shù)的非逃逸參數(shù)傳遞的閉包表達式是非逃逸的,否則,這個閉包表達式是逃逸的。

關(guān)于逃逸閉包的內(nèi)容,請參閱 逃逸閉包。

捕獲列表

默認情況下,閉包會捕獲附近作用域中的常量和變量,并使用強引用指向它們。你可以通過一個捕獲列表來顯式指定它的捕獲行為。

捕獲列表在參數(shù)列表之前,由中括號括起來,里面是由逗號分隔的一系列表達式。一旦使用了捕獲列表,就必須使用 in 關(guān)鍵字,即使省略了參數(shù)名、參數(shù)類型和返回類型。

捕獲列表中的項會在閉包創(chuàng)建時被初始化。每一項都會用閉包附近作用域中的同名常量或者變量的值初始化。例如下面的代碼示例中,捕獲列表包含 a 而不包含 b,這將導致這兩個變量具有不同的行為。

var a = 0
var b = 0
let closure = { [a] in
    print(a, b)
}

a = 10
b = 10
closure()
// 打印“0 10”

在示例中,變量 b 只有一個,然而,變量 a 有兩個,一個在閉包外,一個在閉包內(nèi)。閉包內(nèi)的變量 a 會在閉包創(chuàng)建時用閉包外的變量 a 的值來初始化,除此之外它們并無其他聯(lián)系。這意味著在閉包創(chuàng)建后,改變某個 a 的值都不會對另一個 a 的值造成任何影響。與此相反,閉包內(nèi)外都是同一個變量 b,因此在閉包外改變其值,閉包內(nèi)的值也會受影響。

如果閉包捕獲的值具有引用語義則有所不同。例如,下面示例中,有兩個變量 x,一個在閉包外,一個在閉包內(nèi),由于它們的值是引用語義,雖然這是兩個不同的變量,它們卻都引用著同一實例。

class SimpleClass {
    var value: Int = 0
}
var x = SimpleClass()
var y = SimpleClass()
let closure = { [x] in
    print(x.value, y.value)
}

x.value = 10
y.value = 10
closure()
// 打印“10 10”

如果捕獲列表中的值是類類型,你可以使用 weak 或者 unowned 來修飾它,閉包會分別用弱引用和無主引用來捕獲該值。

myFunction { print(self.title) }                    // 隱式強引用捕獲
myFunction { [self] in print(self.title) }             // 顯式強引用捕獲
myFunction { [weak self] in print(self!.title) }   // 弱引用捕獲
myFunction { [unowned self] in print(self.title) } // 無主引用捕獲

在捕獲列表中,也可以將任意表達式的值綁定到一個常量上。該表達式會在閉包被創(chuàng)建時進行求值,閉包會按照指定的引用類型來捕獲表達式的值。例如:

// 以弱引用捕獲 self.parent 并賦值給 parent
myFunction { [weak parent = self.parent] in print(parent!.title) }

關(guān)于閉包表達式的更多信息和例子,請參閱 閉包表達式。關(guān)于捕獲列表的更多信息和例子,請參閱 解決閉包引起的循環(huán)強引用。

閉包表達式語法

closure-expression

閉包表達式{ 閉包簽名可選 語句 }

closure-signature

閉包簽名參數(shù)子句 函數(shù)結(jié)果可選 *in

閉包簽名標識符列表 函數(shù)結(jié)果可選 in

閉包簽名捕獲列表 參數(shù)子句 函數(shù)結(jié)果可選 in

閉包簽名捕獲列表 標識符列表 函數(shù)結(jié)果可選 in

閉包簽名捕獲列表 in

capture-list

捕獲列表 → [ 捕獲列表項列表 *]

capture-list-items

捕獲列表項列表捕獲列表項 | 捕獲列表項 , 捕獲列表項列表

capture-list-item

捕獲列表項捕獲說明符可選 表達式

capture-specifier

捕獲說明符weak | unowned | unowned(safe) | unowned(unsafe)

隱式成員表達式

若類型可被推斷出來,可以使用隱式成員表達式來訪問某個類型的成員(例如某個枚舉成員或某個類型方法),形式如下:

.成員名稱

例如:

var x = MyEnumeration.SomeValue
x = .AnotherValue

隱式成員表達式語法

implicit-member-expression

隱式成員表達式. 標識符

圓括號表達式

圓括號表達式是由圓括號包圍的表達式。你可以用圓括號說明成組的表達式的先后操作。成組的圓括號不會改變表達式的類型 - 例如 (1) 的類型就是簡單的 Int。

圓括號表達式語法

parenthesized-expression

圓括號表達式( 表達式 )

元組表達式

元組表達式由圓括號和其中多個逗號分隔的子表達式組成。每個子表達式前面可以有一個標識符,用冒號隔開。元組表達式形式如下:

(標識符 1 : 表達式 1, 標識符 2 : 表達式 2, ...)

元組表達式里的每一個標識符在表達式作用域里必須是唯一的。在嵌套的元組表達式中,同嵌套層級里的標識符也必須是唯一的。例如,(a: 10, a: 20) 是不合法的,因為標簽 a 在同一層級出現(xiàn)了兩次。然而,(a: 10, b: (a: 1, x: 2)) 是合法的,盡管 a 出現(xiàn)了兩次,但有一次在外層元組里,一次在內(nèi)層元組里。

元組表達式可以一個表達式都沒有,也可以包含兩個或是更多的表達式。單個表達式用括號括起來就是括號表達式了。

注意

在 Swift 中,空的元組表達式和空的元組類型都寫作 ()。由于 Void() 的類型別名,因此可以使用它來表示空的元組類型。雖然如此,Void 就像所有的類型別名一樣,永遠是一個類型——不能表示空的元組表達式。

元組表達式語法

tuple-expression

元組表達式( ) | (元組元素, 元組元素列表 )

tuple-element-list

元組元素列表元組元素 | 元組元素 , 元組元素列表

tuple-element

元組元素表達式 | 標識符 : 表達式

通配符表達式

通配符表達式可以在賦值過程中顯式忽略某個值。例如下面的代碼中,10 被賦值給 x,而 20 則被忽略:

(x, _) = (10, 20)
// x 為 10,20 被忽略

通配符表達式語法

wildcard-expression

通配符表達式_

Key-path 表達式

Key-path 表達式引用一個類型的屬性或下標。在動態(tài)語言中使場景可以使用 Key-path 表達式,例如觀察鍵值對。格式為:

\類型名.路徑

類型名是一個具體類型的名稱,包含任何泛型參數(shù),例如 String、[Int]Set<Int>。

路徑可由屬性名稱、下標、可選鏈表達式或者強制解包表達式組成。以上任意 key-path 組件可以以任何順序重復多次。

在編譯期,key-path 表達式會被一個 KeyPath 類的實例替換。

對于所有類型,都可以通過傳遞 key-path 參數(shù)到下標方法 subscript(keyPath:) 來訪問它的值。例如:

struct SomeStructure {
    var someValue: Int
}

let s = SomeStructure(someValue: 12)
let pathToProperty = \SomeStructure.someValue

let value = s[keyPath: pathToProperty]
// 值為 12

在一些可以通過類型推斷來確定所訪問的具體類型的上下文中,可以省略 key-path 前的類型名字。下面的代碼使用 \.someProperty 代替了 SomeClass.someProperty

class SomeClass: NSObject {
    @objc var someProperty: Int
    init(someProperty: Int) {
        self.someProperty = someProperty
    }
}

let c = SomeClass(someProperty: 10)
c.observe(\.someProperty) { object, change in
    // ...
}

使用 self 作為路徑可以創(chuàng)建一個恒等 key path (\.self)。恒等 key path 可以作為整個實例的引用,因此你僅需一步操作便可以利用它來訪問以及修改其存儲的所有數(shù)據(jù)。例如:

var compoundValue = (a: 1, b: 2)
// 等價于 compoundValue = (a: 10, b: 20)
compoundValue[keyPath: \.self] = (a: 10, b: 20)

通過點語法,可以讓路徑包含多個屬性名稱,以此來訪問某實例的屬性的屬性。下面的代碼使用 key-path 表達式 \OuterStructure.outer.someValue 來訪問 OuterStructure 類型中 outer 屬性的 someValue 屬性。

struct OuterStructure {
    var outer: SomeStructure
    init(someValue: Int) {
        self.outer = SomeStructure(someValue: someValue)
    }
}

let nested = OuterStructure(someValue: 24)
let nestedKeyPath = \OuterStructure.outer.someValue

let nestedValue = nested[keyPath: nestedKeyPath]
// nestedValue 的值為 24

路徑中也可以包含使用中括號的下標訪問,只要下標訪問的參數(shù)類型滿足 Hashable 協(xié)議即可。下面的例子在 key path 中使用了下標來訪問數(shù)組的第二個元素。

let greetings = ["hello", "hola", "bonjour", "??"]
let myGreeting = greetings[keyPath: \[String].[1]]
// myGreeting 的值為 'hola'

下標訪問中使用的值可以是一個變量或者字面量,并且 key-path 表達式會使用值語義來捕獲此值。下面的代碼在 key-path 表達式和閉包中都使用了 index 變量來訪問 greetings 數(shù)組的第三個元素。當 index 被修改時,key-path 表達式仍舊引用數(shù)組第三個元素,而閉包則使用了新的索引值。

var index = 2
let path = \[String].[index]
let fn: ([String]) -> String = { strings in strings[index] }

print(greetings[keyPath: path])
// 打印 "bonjour"
print(fn(greetings))
// 打印 "bonjour"

// 將 'index' 設置為一個新的值不會影響到 'path'
index += 1
print(greetings[keyPath: path])
// 打印 "bonjour"

// 'fn' 閉包使用了新值。
print(fn(greetings))
// 打印 "??"

路徑可以使用可選鏈和強制解包。下面的代碼在 key path 中使用了可選鏈來訪問可選字符串的屬性。

let firstGreeting: String? = greetings.first
print(firstGreeting?.count as Any)
// 打印 "Optional(5)"

// 使用 key path 實現(xiàn)同樣的功能
let count = greetings[keyPath: \[String].first?.count]
print(count as Any)
// 打印 "Optional(5)"

可以混合使用各種 key-path 組件來訪問一些深度嵌套類型的值。下面的代碼通過組合不同的組件,使用 key-path 表達式訪問了一個字典數(shù)組中不同的值和屬性。

let interestingNumbers = ["prime": [2, 3, 5, 7, 11, 13, 17],
                          "triangular": [1, 3, 6, 10, 15, 21, 28],
                          "hexagonal": [1, 6, 15, 28, 45, 66, 91]]
print(interestingNumbers[keyPath: \[String: [Int]].["prime"]] as Any)
// 打印 "Optional([2, 3, 5, 7, 11, 13, 17])"
print(interestingNumbers[keyPath: \[String: [Int]].["prime"]![0]])
// 打印 "2"
print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count])
// 打印 "7"
print(interestingNumbers[keyPath: \[String: [Int]].["hexagonal"]!.count.bitWidth])
// 打印 "64"

關(guān)于更多如何使用 key path 與 Objective-C APIs 交互的信息,請參閱 在 Swift 中使用 Objective-C 運行時特性。關(guān)于更多 key-value 編程和 key-value 觀察的信息,請參閱 Key-Value 編程Key-Value 觀察編程。

key-path 表達式語法

key-path-expression

key-path 表達式\ 類型可選 . 多個 key-path 組件

key-path-components

多個 key-path 組件key-path 組件 | key-path 組件 . 多個 key-path 組件

key-path-component

key-path 組件標識符 多個 key-path 后綴可選 | 多個 key-path 后綴

key-path-postfixes

多個 key-path 后綴key-path 后綴 多個 key-path 后綴可選 key-path-postfixes

key-path 后綴? | ! | self | [ 函數(shù)調(diào)用參數(shù)表 ]

選擇器表達式

選擇器表達式可以讓你通過選擇器來引用在 Objective-C 中方法(method)和屬性(property)的 setter 和 getter 方法。

#selector(方法名)

#selector(getter: 屬性名) #selector(setter: 屬性名)

方法名和屬性名必須是存在于 Objective-C 運行時中的方法和屬性的引用。選擇器表達式的返回值是一個 Selector 類型的實例。例如:

class SomeClass: NSObject {
    let property: String
    @objc(doSomethingWithInt:)
    func doSomething(_ x: Int) { }

    init(property: String) {
        self.property = property
    }
}
let selectorForMethod = #selector(SomeClass.doSomething(-:))
let selectorForPropertyGetter = #selector(getter: SomeClass.property)

當為屬性的 getter 創(chuàng)建選擇器時,屬性名可以是變量屬性或者常量屬性的引用。但是當為屬性的 setter 創(chuàng)建選擇器時,屬性名只可以是對變量屬性的引用。

方法名稱可以包含圓括號來進行分組,并使用 as 操作符來區(qū)分具有相同方法名但類型不同的方法,例如:

extension SomeClass {
    @objc(doSomethingWithString:)
    func doSomething(_ x: String) { }
}
let anotherSelector = #selector(SomeClass.doSomething(-:) as (SomeClass) -> (String) -> Void)

由于選擇器是在編譯時創(chuàng)建的,因此編譯器可以檢查方法或者屬性是否存在,以及是否在運行時暴露給了 Objective-C 。

注意

雖然方法名或者屬性名是個表達式,但是它不會被求值。

更多關(guān)于如何在 Swift 代碼中使用選擇器來與 Objective-C API 進行交互的信息,請參閱 在 Swift 中使用 Objective-C 運行時特性。

選擇器表達式語法

selector-expression

選擇器表達式 → __#selector-- ( 表達式 )

選擇器表達式 → __#selector-- ( getter:表達式 )

選擇器表達式 → __#selector-- ( setter:表達式 )

Key-path 字符串表達式

key-path 字符串表達式可以訪問一個引用 Objective-C 屬性的字符串,通常在 key-value 編程和 key-value 觀察 APIs 中使用。其格式如下:

#keyPath ( 屬性名 )

屬性名必須是一個可以在 Objective-C 運行時使用的屬性的引用。在編譯期,key-path 字符串表達式會被一個字符串字面量替換。例如:

class SomeClass: NSObject {
    @objc var someProperty: Int
    init(someProperty: Int) {
        self.someProperty = someProperty
    }
}

let c = SomeClass(someProperty: 12)
let keyPath = #keyPath(SomeClass.someProperty)

if let value = c.value(forKey: keyPath) {
    print(value)
}
// 打印 "12"

當在一個類中使用 key-path 字符串表達式時,可以省略類名,直接使用屬性名來訪問這個類的某個屬性。

extension SomeClass {
    func getSomeKeyPath() -> String {
> 
        return #keyPath(someProperty)
    }
}
print(keyPath == c.getSomeKeyPath())
// 打印 "true"

由于 key-path 字符串表達式在編譯期才創(chuàng)建,編譯期可以檢查屬性是否存在,以及屬性是否暴露給 Objective-C 運行時。

關(guān)于更多如何使用 key path 與 Objective-C APIs 交互的信息,請參閱 在 Swift 中使用 Objective-C 運行時特性。關(guān)于更多 key-value 編程和 key-value 觀察的信息,請參閱 Key-Value 編程Key-Value 觀察編程。

注意

盡管屬性名是一個表達式,但它永遠不會被求值

key-path 字符串表達式語法

key-path-string-expression

key-path 字符串表達式#keyPath ( 表達式 )

后綴表達式

后綴表達式就是在某個表達式的后面運用后綴運算符或其他后綴語法。從語法構(gòu)成上來看,基本表達式也是后綴表達式。

關(guān)于這些運算符的更多信息,請參閱 基本運算符高級運算符。

關(guān)于 Swift 標準庫提供的運算符的更多信息,請參閱 運算符定義。

后綴表達式語法

postfix-expression

后綴表達式基本表達式

后綴表達式后綴表達式 后綴運算符

后綴表達式函數(shù)調(diào)用表達式

后綴表達式構(gòu)造器表達式

后綴表達式顯式成員表達式

后綴表達式后綴 self 表達式

后綴表達式dynamicType 表達式

后綴表達式下標表達式

后綴表達式強制取值表達式

后綴表達式可選鏈表達式

函數(shù)調(diào)用表達式

函數(shù)調(diào)用表達式由函數(shù)名和參數(shù)列表組成,形式如下:

函數(shù)名(參數(shù) 1, 參數(shù) 2)

函數(shù)名可以是值為函數(shù)類型的任意表達式。

如果函數(shù)聲明中指定了參數(shù)的名字,那么在調(diào)用的時候也必須得寫出來。這種函數(shù)調(diào)用表達式具有以下形式:

函數(shù)名(參數(shù)名 1: 參數(shù) 1, 參數(shù)名 2: 參數(shù) 2)

如果函數(shù)的最后一個參數(shù)是函數(shù)類型,可以在函數(shù)調(diào)用表達式的尾部(右圓括號之后)加上一個閉包,該閉包會作為函數(shù)的最后一個參數(shù)。如下兩種寫法是等價的:

// someFunction 接受整數(shù)和閉包參數(shù)
someFunction(x, f: {$0 == 13})
someFunction(x) {$0 == 13}

如果閉包是該函數(shù)的唯一參數(shù),那么圓括號可以省略。

// someFunction 只接受一個閉包參數(shù)
myData.someMethod() {$0 == 13}
myData.someMethod {$0 == 13}

函數(shù)調(diào)用表達式語法

function-call-expression

函數(shù)調(diào)用表達式后綴表達式 函數(shù)調(diào)用參數(shù)子句

函數(shù)調(diào)用表達式后綴表達式 函數(shù)調(diào)用參數(shù)子句可選 尾隨閉包

function-call-argument-clause

函數(shù)調(diào)用參數(shù)子句( ) | ( 函數(shù)調(diào)用參數(shù)表 )

function-call-argument-list

函數(shù)調(diào)用參數(shù)表函數(shù)調(diào)用參數(shù) | 函數(shù)調(diào)用參數(shù) , 函數(shù)調(diào)用參數(shù)表

function-call-argument

函數(shù)調(diào)用參數(shù)表達式 | 標識符 : 表達式

函數(shù)調(diào)用參數(shù)運算符 | 標識符 : 運算符

trailing-closure

尾隨閉包閉包表達式

構(gòu)造器表達式

構(gòu)造器表達式用于訪問某個類型的構(gòu)造器,形式如下:

表達式.init(構(gòu)造器參數(shù))

你可以在函數(shù)調(diào)用表達式中使用構(gòu)造器表達式來初始化某個類型的新實例。也可以使用構(gòu)造器表達式來代理給父類構(gòu)造器。

class SomeSubClass: SomeSuperClass {
    override init() {
        // 此處為子類構(gòu)造過程
        super.init()
    }
}

和函數(shù)類似,構(gòu)造器表達式可以作為一個值。 例如:

// 類型注解是必須的,因為 String 類型有多種構(gòu)造器
let initializer: Int -> String = String.init
let oneTwoThree = [1, 2, 3].map(initializer).reduce("", combine: +)
print(oneTwoThree)
// 打印“123”

如果通過名字來指定某個類型,可以不用構(gòu)造器表達式而直接使用類型的構(gòu)造器。在其他情況下,你必須使用構(gòu)造器表達式。

let s1 = SomeType.init(data: 3) // 有效
let s2 = SomeType(data: 1)      // 有效

let s4 = someValue.dynamicType(data: 5)      // 錯誤
let s3 = someValue.dynamicType.init(data: 7) // 有效

構(gòu)造器表達式語法

initializer-expression

構(gòu)造器表達式后綴表達式 . init

構(gòu)造器表達式后綴表達式 . init ( 參數(shù)名稱 )

顯式成員表達式

顯式成員表達式允許我們訪問命名類型、元組或者模塊的成員,其形式如下:

表達式.成員名

命名類型的某個成員在原始實現(xiàn)或者擴展中定義,例如:

class SomeClass {
    var someProperty = 42
}
let c = SomeClass()
let y = c.someProperty // 訪問成員

元組的成員會隱式地根據(jù)表示它們出現(xiàn)順序的整數(shù)來命名,以 0 開始,例如:

var t = (10, 20, 30)
t.0 = t.1
// 現(xiàn)在元組 t 為 (20, 20, 30)

對于模塊的成員來說,只能直接訪問頂級聲明中的成員。

使用 dynamicMemberLookup 屬性聲明的類型包含可以在運行時查找的成員,具體請參閱 屬性

為了區(qū)分只有參數(shù)名有所不同的方法或構(gòu)造器,在圓括號中寫出參數(shù)名,參數(shù)名后緊跟一個冒號,對于沒有參數(shù)名的參數(shù),使用下劃線代替參數(shù)名。而對于重載方法,則需使用類型注解進行區(qū)分。例如:

class SomeClass {
    func someMethod(x: Int, y: Int) {}
    func someMethod(x: Int, z: Int) {}
    func overloadedMethod(x: Int, y: Int) {}
    func overloadedMethod(x: Int, y: Bool) {}
}
let instance = SomeClass()

let a = instance.someMethod              // 有歧義
let b = instance.someMethod(_:y:)        // 無歧義

let d = instance.overloadedMethod        // 有歧義
let d = instance.overloadedMethod(_:y:)  // 有歧義
let d: (Int, Bool) -> Void  = instance.overloadedMethod(_:y:)  // 無歧義

如果點號(.)出現(xiàn)在行首,它會被視為顯式成員表達式的一部分,而不是隱式成員表達式的一部分。例如如下代碼所展示的被分為多行的鏈式方法調(diào)用:

let x = [10, 3, 20, 15, 4]
    .sort()
    .filter { $0 > 5 }
    .map { $0 * 100 }

顯式成員表達式語法

explicit-member-expression

顯式成員表達式后綴表達式 . 十進制數(shù)字

顯式成員表達式后綴表達式 . 標識符 泛型實參子句可選

顯式成員表達式后綴表達式 . 標識符 ( 參數(shù)名稱 )

argument-names

參數(shù)名稱參數(shù)名 參數(shù)名稱可選

argument-name

參數(shù)名標識符 :

后綴 self 表達式

后綴 self 表達式由某個表達式或類型名緊跟 .self 組成,其形式如下:

表達式.self

類型.self

第一種形式返回表達式的值。例如:x.self 返回 x。

第二種形式返回相應的類型。我們可以用它來獲取某個實例的類型作為一個值來使用。例如,SomeClass.self 會返回 SomeClass 類型本身,你可以將其傳遞給相應函數(shù)或者方法作為參數(shù)。

后綴 self 表達式語法

postfix-self-expression

后綴 self 表達式后綴表達式 . self

下標表達式

可通過下標表達式訪問相應的下標,形式如下:

表達式[索引表達式]

要獲取下標表達式的值,可將索引表達式作為下標表達式的參數(shù)來調(diào)用下標 getter。下標 setter 的調(diào)用方式與之一樣。

關(guān)于下標的聲明,請參閱 協(xié)議下標聲明

下標表達式語法

subscript-expression

下標表達式后綴表達式 [ 表達式列表 ]

強制取值表達式

當你確定可選值不是 nil 時,可以使用強制取值表達式來強制解包,形式如下:

表達式!

如果該表達式的值不是 nil,則返回解包后的值。否則,拋出運行時錯誤。

返回的值可以被修改,無論是修改值本身,還是修改值的成員。例如:

var x: Int? = 0
x!++
// x 現(xiàn)在是 1

var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]
someDictionary["a"]![0] = 100
// someDictionary 現(xiàn)在是 [b: [10, 20], a: [100, 2, 3]]

強制取值語法

forced-value-expression

強制取值表達式后綴表達式 !

可選鏈表達式

可選鏈表達式提供了一種使用可選值的便捷方法,形式如下:

表達式?

后綴 ? 運算符會根據(jù)表達式生成可選鏈表達式而不會改變表達式的值。

如果某個后綴表達式包含可選鏈表達式,那么它的執(zhí)行過程會比較特殊。如果該可選鏈表達式的值是 nil,整個后綴表達式會直接返回 nil。如果該可選鏈表達式的值不是 nil,則返回可選鏈表達式解包后的值,并將該值用于后綴表達式中剩余的表達式。在這兩種情況下,整個后綴表達式的值都會是可選類型。

如果某個后綴表達式中包含了可選鏈表達式,那么只有最外層的表達式會返回一個可選類型。例如,在下面的例子中,如果 c 不是 nil,那么它的值會被解包,然后通過 .property 訪問它的屬性,接著進一步通過 .performAction() 調(diào)用相應方法。整個 c?.property.performAction() 表達式返回一個可選類型的值,而不是多重可選類型。

var c: SomeClass?
var result: Bool? = c?.property.performAction()

上面的例子跟下面的不使用可選鏈表達式的例子等價:

var result: Bool? = nil
if let unwrappedC = c {
    result = unwrappedC.property.performAction()
}

可選鏈表達式解包后的值可以被修改,無論是修改值本身,還是修改值的成員。如果可選鏈表達式的值為 nil,則表達式右側(cè)的賦值操作不會被執(zhí)行。例如:

func someFunctionWithSideEffects() -> Int {
    // 譯者注:為了能看出此函數(shù)是否被執(zhí)行,加上了一句打印
    print("someFunctionWithSideEffects")
    return 42
}
var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]

someDictionary["not here"]?[0] = someFunctionWithSideEffects()
// someFunctionWithSideEffects 不會被執(zhí)行
// someDictionary 依然是 ["b": [10, 20], "a": [1, 2, 3]]

someDictionary["a"]?[0] = someFunctionWithSideEffects()
// someFunctionWithSideEffects 被執(zhí)行并返回 42
// someDictionary 現(xiàn)在是 ["b": [10, 20], "a": [42, 2, 3]]

可選鏈表達式語法

optional-chaining-expression

可選鏈表達式后綴表達式 ?

? 類型 語句 ?
?