swift - 基础语法(只为防困学习记录)

346 阅读33分钟

基础语法

感觉这部分直接看任何一个入门的就可以,以下主要是,记录自己以前没用到和不熟悉的语法和用法

数据类型

类型别名

typealias newname = type

typealias MyInt = Int
var myAge : MyInt = 24
print(myAge)
类型安全

所谓类型安全,就是会在编译代码时候进行类型检查(type checks),并把不匹配的类型标记为错误。可以在开发时候发现并修复错误。

var myVal = 42
myVal = "This is hello"
print(myVal)

程序会报错

error : cannot assign value of type 'String' to type 'Int'
myVal = "This is Hello"

因为编译器最初认定myValInt类型,所以不能将String类型的值赋值给Int类型的变量

类型推断

即我们可以不用显示的指定类型,Swift会使用类型推断来选择合适的类型

var myAge = 23
// myAge 会被推断为Int类型
let myWeight = 60.5134
// myWeight 会被推测Double类型

let myPower = myAge + myWeight
// 表达式中出现两种类型整数和浮点,这样就会被升级为浮点的Double类型

变量

声明变量
var varName = <#varName Value#>
变量命名
/*
字母数字下划线组成,
以字母和下划线开始,
区分大小写,
可以使用Unicode字符
*/
import Cocoa

var _hello = "Hello"
print(_hello)

var 哈喽 = "哈喽"
var 教程 = "www.baidu.com"
print(哈喽)
print(教程)

打印结果

Hello
哈喽
www.baidu.com

可选类型

可选类型的声明

// 这两种声明只是等价的
var optionalInt:Int? // Int和?之间没有空格
var optionalInt:Optional<Int>

Optional是一个含有两种情况的枚举,NoneSome(T),分别便是可能没有值和可能有值。任何类型都可以声明为(或者隐式转换)可选类型。声明一个可选类型的时候,要确保用括号给?操作符一个合适的范围

// 声明可选整数数组
var myList : (Int[])? 

//以下声明就不明确了,会报错
var myList : Int[]?

当声明一个可选类型变量或者可选属性的时候没有提供初始值,默认会为nil

可选项遵照LogicValue协议,因此可以出现在布尔环境中。在这种情况中,如果可选类型T?包含类型为T的任何值,即它的值为Optiona.Some(T),则这个可选类型等于true,反之为false

optionalInt = 42
// 如果一个可选类型的实例包含一个值,可以用后缀操作符`!`来访问这个值
optionalInt! // 42

使用操作符!去获取值为nil的可选变量会有运行时错误

这时候可以用可选链接和可选绑定选择性执行可选表达式上的操作,如果值为nil,任何操作都不会执行,也不会有运行报错

import Cocoa
var myString : String? = nil
if myString != nil{
    print(myString)
}else {
    print("字符串为 nil")
}

执行结果为字符串为 nil 可选类型类似于Objective-C中指针的nil的值,但是nil只对类有用,而可选类型对所有的类型都可用,并且更安全

强制解析

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示"我知道这个可选有值,请使用它。"这被称为可选值的强制解析(forced unwrapping)。

a.s.

import Cocoa
var myString:String?

myString = "Hello,Swift!"
if myString != nil{
    print(myString)
}else{
    print("myString is nil")
}

执行结果为Optional("Hello,Swift!")

强制解析可选项值,使用感叹号(!)

import Cocoa
var myString:String?

myString = "Hello,Swift!"
if myString != nil{
    print(myString!) // 注意这一行是强制解析
}else{
    print("myString is nil")
}

执行结果是Hello,Swift!

使用!来获取一个不存在的可选项值会导致运行时错误,使用!来强制解析值之前,一定确定可选包含一个非nil的值

自动解析

可以在声明可选变量时使用感叹号!替换问号?,这样可选变量在使用时就不需要再加一个感叹号!来获取值,它会自动解析

import Cocoa

var myString:String!

myString = "Hello, Swift!"

if myString != nil {
   print(myString)
}else{
   print("myString 值为 nil")
}


以上程序执行结果为:

Hello, Swift!
可选绑定

使用可选绑定来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋值给一个常量或者变量

if let constantName = SomeOptional{
    statements
}

让我们来看下一个简单的可选绑定实例:

import Cocoa

var myString:String?

myString = "Hello, Swift!"

if let yourString = myString {
   print("你的字符串值为 - \(yourString)")
}else{
   print("你的字符串没有值")
}

以上程序执行结果为:

你的字符串值为 - Hello, Swift!

运算符

区间运算符
闭区间运算符
  • (a...b)定义一个包含从a到b(包括a和b)的所有的值的区间,b必须大于等于a 1...5
  • 半开区间(a.. 1..<5 === 1,2,3,4
import Cocoa

print("闭区间运算符:")
for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}

print("半开区间运算符:")
for index in 1..<5 {
    print("\(index) * 5 = \(index * 5)")
}

以上程序执行结果为:

闭区间运算符:
1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20
5 * 5 = 25
半开区间运算符:
1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20

字符串

创建字符串
import Cocoa

// 使用字符串字面量
var stringA = "Hello, World!"
print( stringA )

// String 实例化
var stringB = String("Hello, World!")
print( stringB )

以上程序执行输出结果为:

Hello, World!
Hello, World!
空字符串

可以使用字符串属性 isEmpty 来判断字符串是否为空:

import Cocoa

// 使用字符串字面量创建空字符串
var stringA = ""

if stringA.isEmpty {
   print( "stringA 是空的" )
} else {
   print( "stringA 不是空的" )
}

// 实例化 String 类来创建空字符串
let stringB = String()

if stringB.isEmpty {
   print( "stringB 是空的" )
} else {
   print( "stringB 不是空的" )
}

以上程序执行输出结果为:

stringA 是空的
stringB 是空的
字符串常量

你可以将一个字符串赋值给一个变量或常量,变量是可修改的,常量是不可修改的。

import Cocoa

// stringA 可被修改
var stringA = "菜鸟教程:"
stringA += "http://www.runoob.com"
print( stringA )

// stringB 不能修改
let stringB = String("菜鸟教程:")
stringB += "http://www.runoob.com"
print( stringB )

以上程序执行输出结果会报错,因为 stringB 为常量是不能被修改的:

error: left side of mutating operator isn't mutable: 'stringB' is a 'let' constant
stringB += "http://www.runoob.com"
字符串中插入值

字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。 您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:

import Cocoa

var varA   = 20
let constA = 100
var varC:Float = 20.0

var stringA = "\(varA) 乘于 \(constA) 等于 \(varC * 100)"
print( stringA )

以上程序执行输出结果为:

20 乘于 100 等于 2000.0
字符串连接

字符串可以通过 + 号来连接,实例如下:

import Cocoa

let constA = "菜鸟教程:"
let constB = "http://www.runoob.com"

var stringA = constA + constB

print( stringA )

以上程序执行输出结果为:

菜鸟教程:http://www.runoob.com
字符串使用的是String.count属性计算字符串长度的
import Cocoa

var varA   = "www.runoob.com"

print( "\(varA), 长度为 \(varA.count)" ) // 输出长度是14
字符串比较

可以使用 == 比较两个字符串是否相等

import Cocoa

var varA   = "Hello, Swift!"
var varB   = "Hello, World!"

if varA == varB {
   print( "\(varA)\(varB) 是相等的" )
} else {
   print( "\(varA)\(varB) 是不相等的" ) // 输出的是这个 不等
}
Unicode 字符串
import Cocoa

var unicodeString   = "菜鸟教程"

print("UTF-8 编码: ")
for code in unicodeString.utf8 {
   print("\(code) ")
}

print("\n")

print("UTF-16 编码: ")
for code in unicodeString.utf16 {
   print("\(code) ")
}

以上程序执行输出结果为:

<font color=#00ffff>
UTF-8 编码: 
232 
143 
156 
233 
184 
159 
230 
149 
153 
231 
168 
139 
UTF-16 编码: 
33756 
40479 
25945 
31243 

字符串是值类型 SwiftString类型是一种值类型。如果你创建了一个新的 String值, String值在传递给方法 或者函数的时候会被复制过去,还有赋值给常量或者变量的时候也是一样。每一次赋值和传 递,现存的 String值都会被复制一次,传递走的是拷贝而不是原本。

字符

Swift 的字符是一个单一的字符字符串字面量,数据类型为 Character。

import Cocoa

let char1: Character = "A"
let char2: Character = "B"

print("char1 的值为 \(char1)")
print("char2 的值为 \(char2)")
// 以上程序执行输出结果为:

// char1 的值为 A
// char2 的值为 B

在 Character(字符) 类型的常量中存储更多的字符,则程序执行会报错 Swift 中不能创建空的 Character(字符) 类型变量或常量,则程序执行会报错

遍历字符串中的字符 Swift 的 String 类型表示特定序列的 Character(字符) 类型值的集合。 每一个字符值代表一个 Unicode 字符。

您可通过for-in循环来遍历字符串中的characters属性来获取每一个字符的值:

import Cocoa

for ch in "Runoob".characters {
    print(ch)
}

字符串连接字符 以下实例演示了使用 String 的 append() 方法来实现字符串连接字符:

import Cocoa

var varA:String = "Hello "
let varB:Character = "G"

varA.append( varB )

print("varC  =  \(varA)")

数组

Swift 数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。 Swift 数组会强制检测元素的类型,如果类型不同则会报错,Swift 数组应该遵循像Array这样的形式,其中Element是这个数组中唯一允许存在的数据类型。 如果创建一个数组,并赋值给一个变量,则创建的集合就是可以修改的。这意味着在创建数组后,可以通过添加、删除、修改的方式改变数组里的项目。如果将一个数组赋值给常量,数组就不可更改,并且数组的大小和内容都不可以修改。

创建一个空数组
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// 打印“someInts is of type [Int] with 0 items.”

通过构造函数的类型,someInts 的值类型被推断为 [Int]

如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,你可以使用空数组语句创建一个空数组,它的写法很简单:[](一对空方括号):

someInts.append(3)
// someInts 现在包含一个 Int 值
someInts = []
// someInts 现在是空数组,但是仍然是 [Int] 类型的。
创建一个带有默认值的数组

Swift 中的 Array 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。可以把准备加入新数组的数据项数量(count)和适当类型的初始值(repeating)传入数组构造函数:

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]
用数组字面量构造数组

你可以使用数组字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值: [value 1, value 2, value 3]。 下面这个例子创建了一个叫做 shoppingList 并且存储 String 的数组:

var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList 已经被构造并且拥有两个初始项。

shoppingList 变量被声明为“字符串值类型的数组“,记作 [String]。因为这个数组被规定只有 String 一种数据结构,所以只有 String 类型可以在其中被存取。在这里,shoppingList 数组由两个 String 值("Eggs""Milk")构造,并且由数组字面量定义。

访问和修改数组

你可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。 可以使用数组的只读属性 count 来获取数组中的数据项数量:

print("The shopping list contains \(shoppingList.count) items.")
// 输出“The shopping list contains 2 items.”(这个数组有2个项)

使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:

if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}

// 打印“The shopping list is not empty.”(shoppinglist 不是空的)

也可以使用 append(_:) 方法在数组后面添加新的数据项:

shoppingList.append("Flour")
// shoppingList 现在有3个数

据项,似乎有人在摊煎饼 除此之外,也可以使用加法赋值运算符(+=)直接将另一个相同类型数组中的数据添加到该数组后面:

shoppingList += ["Baking Powder"]
// shoppingList 现在有四项了
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList 现在有七项了

可以直接使用下标语法来获取数组中的数据项,把所需要数据项的索引值直接放在数组名称之后的方括号中:

var firstItem = shoppingList[0]
// 第一项是“Eggs”

你也可以用下标来改变某个有效索引值对应的数据值:

shoppingList[0] = "Six eggs"
// 其中的第一项现在是“Six eggs”而不是“Eggs”

还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 "Chocolate Spread""Cheese""Butter" 替换为 "Bananas""Apples"


shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList 现在有6项

通过调用数组的 insert(_:at:) 方法在某个指定索引值之前添加数据项:

shoppingList.insert("Maple Syrup", at: 0)
// shoppingList 现在有7项
// 现在是这个列表中的第一项是“Maple Syrup”

这次 insert(_:at:) 方法调用把值为 "Maple Syrup" 的新数据项插入列表的最开始位置,并且使用 0 作为索引值。

类似的可以使用 remove(at:) 方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(不需要的时候就可以无视它):

let mapleSyrup = shoppingList.remove(at: 0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项,而且不包括 Maple Syrup
// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”

数据项被移除后数组中的空出项会被自动填补,所以现在索引值为 0 的数据项的值再次等于 "Six eggs"

firstItem = shoppingList[0]
// firstItem 现在等于“Six eggs”

如果你只想把数组中的最后一项移除,可以使用 removeLast() 方法而不是 remove(at:) 方法来避免需要获取数组的 count 属性。就像后者一样,前者也会返回被移除的数据项:

let apples = shoppingList.removeLast()
// 数组的最后一项被移除了
// shoppingList 现在只有5项,不包括 Apples
// apples 常量的值现在等于字符串“Apples”
数组的遍历

你可以使用 for-in 循环来遍历数组中所有的数据项:

for item in shoppingList {
    print(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas

如果同时需要每个数据项的值和索引值,可以使用 enumerated() 方法来进行数组遍历。enumerated() 返回一个由索引值和数据值组成的元组数组。索引值从零开始,并且每次增加一;如果枚举一整个数组,索引值将会和数据值一一匹配。你可以把这个元组分解成临时常量或者变量来进行遍历:

for (index, value) in shoppingList.enumerated() {
    print("Item \(String(index + 1)): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas

集合

集合用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。 注意 SwiftSet 类型被桥接到 Foundation 中的 NSSet 类。 关于使用 FoundationCocoaSet 的知识,参见 Bridging Between Set and NSSet

集合类型的哈希值

一个类型为了存储在集合中,该类型必须是可哈希化的——也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是 Int 类型的,相等的对象哈希值必须相同,比如 a == b,因此必须 a.hashValue == b.hashValueSwift 的所有基本类型(比如 StringIntDoubleBool)默认都是可哈希化的,可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值(在 枚举 有讲述)默认也是可哈希化的。

注意

你可以使用自定义的类型作为集合值的类型或者是字典键的类型,但需要使自定义类型遵循 Swift 标准库中的 Hashable 协议。遵循 Hashable 协议的类型需要提供一个类型为 Int 的可读属性hashValue。由类型的hashValue属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。 因为Hashable协议遵循 Equatable协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(==)的实现。这个Equatable协议要求任何遵循==实现的实例间都是一种相等的关系。也就是说,对于a,b,c三个值来说,==` 的实现必须满足下面三种情况:

  • a == a(自反性)
  • a == b 意味着 b == a(对称性)
  • a == b && b == c 意味着 a == c(传递性) 关于遵循协议的更多信息,请看 协议
集合类型语法

Swift 中的集合类型被写为 Set<Element>,这里的 Element 表示集合中允许存储的类型。和数组不同的是,集合没有等价的简化形式。

创建和构造一个空的集合

你可以通过构造器语法创建一个特定类型的空集合:

var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// 打印“letters is of type Set<Character> with 0 items.”

注意

通过构造器,这里 letters 变量的类型被推断为 Set<Character>

如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,你可以通过一个空的数组字面量创建一个空的集合:

letters.insert("a")
// letters 现在含有1个 Character 类型的值
letters = []
// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型
用数组字面量创建集合

你可以使用数组字面量来构造集合,相当于一种简化的形式将一个或者多个值作为集合元素。 下面的例子创建一个称之为 favoriteGenres 的集合来存储 String 类型的值:

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres 被构造成含有三个初始值的集合

这个 favoriteGenres 变量被声明为“一个 String 值的集合”,写为 Set<String>。由于这个特定集合指定了值为 String 类型,所以它只允许存储 String 类型值。这里的 favoriteGenres 变量有三个 String 类型的初始值("Rock","Classical" 和 "Hip hop"),以数组字面量的形式书写。

注意

favoriteGenres 被声明为一个变量(拥有 var 标示符)而不是一个常量(拥有 let 标示符),因为它里面的元素将会在之后的例子中被增加或者移除。 一个集合类型不能从数组字面量中被直接推断出来,因此 Set 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个集合并且与该数组字面量中的所有元素类型相同,那么无须写出集合的具体类型。favoriteGenres 的构造形式可以采用简化的方式代替: var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] 由于数组字面量中的所有元素类型相同,Swift 可以推断出 Set<String> 作为 favoriteGenres 变量的正确类型。

访问和修改一个集合

你可以通过集合的属性和方法来对其进行访问和修改。 为了获取一个集合中元素的数量,可以使用其只读属性 count

print("I have \(favoriteGenres.count) favorite music genres.")
// 打印“I have 3 favorite music genres.”

使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:

if favoriteGenres.isEmpty {
    print("As far as music goes, I'm not picky.")
} else {
    print("I have particular music preferences.")
}
// 打印“I have particular music preferences.”
你可以通过调用集合的 insert(_:) 方法来添加一个新元素:
favoriteGenres.insert("Jazz")
// favoriteGenres 现在包含4个元素

你可以通过调用集合的 remove(_:) 方法去删除一个元素,如果它是该集合的一个元素则删除它并且返回它的值,若该集合不包含它,则返回 nil。另外,集合可以通过 removeAll() 方法删除所有元素。

if let removedGenre = favoriteGenres.remove("Rock") {
    print("\(removedGenre)? I'm over it.")
} else {
    print("I never much cared for that.")
}
// 打印“Rock? I'm over it.”

使用 contains(_:) 方法去检查集合中是否包含一个特定的值:

if favoriteGenres.contains("Funk") {
    print("I get up on the good foot.")
} else {
    print("It's too funky in here.")
}
// 打印“It's too funky in here.”

SwiftSet 类型没有确定的顺序,为了按照特定顺序来遍历一个集合中的值可以使用 sorted() 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符 <对元素进行比较的结果来确定。

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz
集合的操作
基本集合操作

下面的插图描述了两个集合 ab,以及通过阴影部分的区域显示集合各种操作的结果。

集合的操作.png

  • 使用 intersection(_:) 方法根据两个集合的交集创建一个新的集合。
  • 使用 symmetricDifference(_:) 方法根据两个集合不相交的值创建一个新的集合。
  • 使用union(_:) 方法根据两个集合的所有值创建一个新的集合。
  • 使用 subtracting(_:) 方法根据不在另一个集合中的值创建一个新的集合。
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]
集合成员关系和相等

下面的插图描述了三个集合 abc,以及通过重叠区域表述集合间共享的元素。集合 a 是集合 b 的父集合,因为 a 包含了 b中所有的元素。相反的,集合 b 是集合 a 的子集合,因为属于 b 的元素也被 a 包含。集合 b 和集合 c 是不相交的,因为它们之间没有共同的元素。

  • 使用“是否相等”运算符(==)来判断两个集合包含的值是否全部相同。
  • 使用 isSubset(of:) 方法来判断一个集合中的所有值是否也被包含在另外一个集合中。
  • 使用 isSuperset(of:) 方法来判断一个集合是否包含另一个集合中所有的值。
  • 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
  • 使用 isDisjoint(with:) 方法来判断两个集合是否不含有相同的值(是否没有交集)。
let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

字典

字典类型简化语法

Swift 的字典使用 Dictionary<Key, Value>定义,其中 Key 是一种可以在字典中被用作键的类型,Value 是字典中对应于这些键所存储值的数据类型。

注意

一个字典的 Key 类型必须遵循 Hashable 协议,就像 Set 的值类型。 你也可以用 [Key: Value] 这样简化的形式去表示字典类型。虽然这两种形式功能上相同,但是后者是首选,并且本教程中涉及到字典类型时通篇采用后者。

创建一个空字典

你可以像数组一样使用构造语法创建一个拥有确定类型的空字典:

var namesOfIntegers = [Int: String]()
// namesOfIntegers 是一个空的 [Int: String] 字典

这个例子创建了一个 [Int: String] 类型的空字典来储存整数的英语命名。它的键是 Int 型,值是 String 型。 如果上下文已经提供了类型信息,你可以使用空字典字面量来创建一个空字典,记作 [:] (一对方括号中放一个冒号):

namesOfIntegers[16] = "sixteen"
// namesOfIntegers 现在包含一个键值对
namesOfIntegers = [:]
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
用字典字面量创建字典

你可以使用字典字面量来构造字典,这和刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 Dictionary 集合的快捷途径。 一个键值对是一个键和一个值的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由逗号分割、并整体被包裹在一对方括号中: [key 1: value 1, key 2: value 2, key 3: value 3] 下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称:

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

airports 字典被声明为一种 [String: String] 类型,这意味着这个字典的键和值都是 String 类型。

访问和修改字典

你可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。 和数组一样,可以通过 Dictionary 的只读属性 count 来获取字典的数据项数量:

print("The dictionary of airports contains \(airports.count) items.")
// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项)

使用布尔属性 isEmpty 作为一个缩写形式去检查 count 属性是否为 0:

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}
// 打印“The airports dictionary is not empty.”

你可以通过下标语法来给字典添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值:

airports["LHR"] = "London"
// airports 字典现在有三个数据项

也可以使用下标语法来改变特定键对应的值:

airports["LHR"] = "London Heathrow"
// “LHR”对应的值被改为“London Heathrow”

作为一种替代下标语法的方式,字典的 updateValue(_:forKey:) 方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,updateValue(_:forKey:) 方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和下标的方式不同,updateValue(_:forKey:) 这个方法返回更新值之前的原值。这样使得你可以检查更新是否成功。 updateValue(_:forKey:) 方法会返回对应值类型的可选类型。举例来说:对于存储 String 值的字典,这个函数会返回一个 String? 或者“可选 String”类型的值。如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 nil

if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
    print("The old value for DUB was \(oldValue).")
}
// 输出“The old value for DUB was Dublin.”

你也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值类型的可选类型。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选类型,否则将返回 nil

if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} else {
    print("That airport is not in the airports dictionary.")
}
// 打印“The name of the airport is Dublin Airport.”

还可以使用下标语法通过将某个键的对应值赋值为 nil 来从字典里移除一个键值对:

airports["APL"] = "Apple Internation"
// “Apple Internation”不是真的 APL 机场,删除它
airports["APL"] = nil
// APL 现在被移除了

此外,removeValue(forKey:) 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有对应值的情况下返回 nil

if let removedValue = airports.removeValue(forKey: "DUB") {
    print("The removed airport's name is \(removedValue).")
} else {
    print("The airports dictionary does not contain a value for DUB.")
}
// 打印“The removed airport's name is Dublin Airport.”
字典遍历
  • For

你可以使用 for-in 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 (key, value) 元组形式返回,并且可以使用临时常量或者变量来分解这些元组:

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson
// LHR: London Heathrow

通过访问 keys 或者 values 属性,你也可以遍历字典的键或者值:

for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}
// Airport code: YYZ
// Airport code: LHR

for airportName in airports.values {
    print("Airport name: \(airportName)")
}
// Airport name: Toronto Pearson
// Airport name: London Heathrow

如果你需要使用某个字典的键集合或者值集合来作为某个接受 Array 实例的 API 的参数,可以直接使用 keys 或者 values 属性构造一个新数组:

let airportCodes = [String](airports.keys)
// airportCodes 是 ["YYZ", "LHR"]

let airportNames = [String](airports.values)
// airportNames 是 ["Toronto Pearson", "London Heathrow"]

SwiftDictionary 是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 keysvalues 属性使用 sorted() 方法。

控制流程

  • while
  • if
  • guard
  • switch
  • break
  • continue
  • fallthrought
  • for-in
  • switch

如果你不需要区间序列内每一项的值,你可以使用下划线(_)替代变量名来忽略这个值:

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 输出“3 to the power of 10 is 59049”

一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 stride(from:to:by:) 函数跳过不需要的标记。

let minutes = 60
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // 每5分钟渲染一个刻度线(0, 5, 10, 15 ... 45, 50, 55)
}

可以在闭区间使用 stride(from:through:by:) 起到同样作用:

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // 每3小时渲染一个刻度线(3, 6, 9, 12)
}

  • While

    • while
    • repeat-while
  • switch 区间匹配

case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// 输出“There are dozens of moons orbiting Saturn.”

我们可以使用元组在同一个 switch 语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(_)来匹配所有可能的值。 下面的例子展示了如何使用一个(Int, Int) 类型的元组来分类下图中的点(x, y)

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// 输出“(1, 1) is inside the box”

值绑定(Value Bindings)

case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用 —— 这种行为被称为值绑定(value binding),因为匹配的值在 case 分支体内,与临时的常量或变量绑定。 下面的例子将下图中的点 (x, y),使用 (Int, Int) 类型的元组表示,然后分类表示:

let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// 输出“on the x-axis with an x value of 2”

值绑定 Where(Value Bindings)

case 分支的模式可以使用 where 语句来判断额外的条件。 下面的例子把下图中的点 (x, y)进行了分类:

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}
// 输出“(1, -1) is on the line x == -y”

复合型 Cases(Value Bindings)

当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 case 后面,并且用逗号隔开。当 case 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
     "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) is not a vowel or a consonant")
}
// 输出“e is a vowel”

复合匹配 值绑定 复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// 输出“On an axis, 9 from the origin”

上面的 case 有两个模式:(let distance, 0) 匹配了在 x 轴上的值,(0, let distance) 匹配了在 y 轴上的值。两个模式都绑定了 distance,并且 distance 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 distance 值。

控制转移语句
  • continue
  • break
  • fallthrough
  • return
  • throw

注意 fallthrough 关键字不会检查它下一个将会落入执行的 case 中的匹配条件。fallthrough 简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的 switch 语句特性是一样的。

带标签的语句

显式地指明 break 语句想要终止的是哪个循环体或者条件语句,会很有用;如果你有许多嵌套的循环体,显式指明 continue 语句想要影响哪一个循环体也会非常有用

为了实现这个目的,你可以使用标签(statement label)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 break 加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用 break 或者 continue 加标签,来结束或者继续这条被标记语句的执行。

声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对 while 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。

label name: while condition {
     statements
 }

提前退出

if语句一样,guard 的执行取决于一个表达式的布尔值。我们可以使用 guard 语句来要求条件必须为真时,以执行 guard 语句后的代码。不同于 if 语句,一个 guard 语句总是有一个 else 从句,如果条件不为真则执行 else 从句中的代码。

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.”

如果 guard 语句的条件被满足,则继续执行 guard 语句大括号后的代码。将变量或者常量的可选绑定作为 guard 语句的条件,都可以保护 guard 语句后面的代码。 如果条件不被满足,在 else 分支上的代码就会被执行。这个分支必须转移控制以退出 guard 语句出现的代码段。它可以用控制转移语句如 returnbreakcontinue 或者 throw 做这件事,或者调用一个不返回的方法或函数,例如 fatalError()。 相比于可以实现同样功能的 if 语句,按需使用 guard 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 else 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。

检测 API 可用性

Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 API。 编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 APISwift 会在编译时报错。 我们在 ifguard 语句中使用 可用性条件(availability condition)去有条件的执行一段代码,来在运行时判断调用的 API 是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。

if #available(iOS 10, macOS 10.12, *) {
    // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
    // 使用先前版本的 iOS 和 macOS 的 API
}

以上可用性条件指定,if 语句的代码块仅仅在 iOS 10macOS 10.12 及更高版本才运行。最后一个参数,*,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。 在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 iOS``,macOSwatchOStvOS——请访问 声明属性 来获取完整列表。除了指定像 iOS 8macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。

if #available(平台名称 版本号, ..., *) {
    APIs 可用,语句将执行
} else {
    APIs 不可用,语句将不执行
}