基础部分(The Basics)
- 当推断浮点数的类型时,Swift 总是会选择
Double
而不是Float
。 - 结合数字类常量和变量不同于结合数字类字面量。字面量3可以直接和字面量0.14159相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
-
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用
typealias
关键字来定义类型别名。
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min -
元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
let http404Error = (404, "Not Found")
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
let (statusCode, statusMessage) = http404Error
print("The status code is (statusCode)")
// 输出 "The status code is 404"
print("The status message is (statusMessage)")
// 输出 "The status message is Not Found
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(_)标记:
let (justTheStatusCode, _) = http404Error
print("The status code is (justTheStatusCode)")
// 输出 "The status code is 404
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
print("The status code is (http404Error.0)")
// 输出 "The status code is 404"
print("The status message is (http404Error.1)")
// 输出 "The status message is Not Found
你可以在定义元组的时候给单个元素命名:
let http200Status = (statusCode: 200, description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is (http200Status.statusCode)")
// 输出 "The status code is 200"
print("The status message is (http200Status.description)")
// 输出 "The status message is OK - 使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示:
有值,等于 x;或者,没有值
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为nil:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil”
注意:
nil
不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
Swift 的nil
和 Objective-C 中的nil
并不一样。在 Objective-C 中,nil
是一个指向不存在对象的指针。在 Swift 中,nil
不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为nil
,不只是对象类型。
空字符串(如"")和一个值为nil
的可选类型的字符串是两个完全不同的概念。
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(String?
)改成感叹号(String!
)来声明一个隐式解析可选类型。
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要惊叹号来获取值
let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 不需要感叹号
- 你可以使用全局
assert(_:_file:line:)
函数来写一个断言。向这个函数传入一个结果为true或者false的表达式以及一条信息,当表达式的结果为false的时候这条信息会被显示并终止程序:
let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
//因为age<0,所以断言会触发
基本运算符(Basic Operators)
- 在对负数 b 求余时,b 的符号会被忽略。这意味着
a % b
和a % -b
的结果是相同的。 -
空合运算符(
a ?? b
)将对可选类型a
进行空判断,如果a包含一个值就进行解封,否则就返回一个默认值b
.这个运算符有两个条件:1.表达式a
必须是Optional类型 2.默认值b
的类型必须要和a存储值的类型保持一致” -
闭区间运算符(
a...b
)定义一个包含从a
到b
(包括a
和b
)的所有值的区间,b
必须大于等于a
。
半开区间(a..<b
)定义一个从a
到b
但不包括b
的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。 - 逻辑非(
!a
)
逻辑与(a && b
)
逻辑或(a || b
)
字符串和字符(Strings and Characters)
- Swift 的
String
类型是值类型。 如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。 任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。 - 您可通过
for-in
循环来遍历字符串中的characters
属性来获取每一个字符的值:
for character in "Dog!🐶".characters {
print(character)
}
// D
// o
// g
// !
// 🐶 - 您可以用
append()
方法将一个字符附加到一个字符串变量的尾部:
let welcome = "hello there"
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome 现在等于 "hello there!
注意: 您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
-
访问和修改字符串 (Accessing and Modifying a String)
Swift 的字符串不能用整数(integer)做索引。
使用startIndex
属性可以获取一个String
的第一个Character
的索引。使用endIndex
属性可以获取最后一个Character
的后一个位置的索引。因此,endIndex
属性不能作为一个字符串的有效下标。如果String
是空串,startIndex
和endIndex
是相等的。
通过调用String.Index
的predecessor()
方法,可以立即得到前面一个索引,调用successor()
方法可以立即得到后面一个索引。任何一个String
的索引都可以通过锁链作用的这些方法来获取另一个索引,也可以调用advancedBy(_:)
方法来获取。但如果尝试获取出界的字符串索引,就会抛出一个运行时错误。
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.endIndex.predecessor()]
// !
greeting[greeting.startIndex.successor()]
// u
let index = greeting.startIndex.advancedBy(7)
greeting[index]
// a
使用characters
属性的indices
属性会创建一个包含全部索引的范围(Range
),用来在一个字符串中访问单个字符。
for index in greeting.characters.indices {
print("(greeting[index]) ", terminator: " ")
}
// 打印输出 "G u t e n T a g !
调用insert(_:atIndex:)
方法可以在一个字符串的指定索引插入一个字符。
var welcome = "hello"
welcome.insert("!", atIndex: welcome.endIndex)
// welcome now 现在等于 "hello!"
调用insertContentsOf(_:at:)
方法可以在一个字符串的指定索引插入一个字符串。
welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor())
// welcome 现在等于 "hello there!"
调用removeAtIndex(_:)
方法可以在一个字符串的指定索引删除一个字符。
welcome.removeAtIndex(welcome.endIndex.predecessor())
// welcome 现在等于 "hello there"
调用removeRange(_:)
方法可以在一个字符串的指定索引删除一个子字符串。
let range = welcome.endIndex.advancedBy(-6)..<welcome.endIndex
welcome.removeRange(range)
// welcome 现在等于 "hello"
-
比较字符串 (Comparing Strings)
通过调用字符串的hasPrefix(:)/hasSuffix(:)方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个String类型的参数,并返回一个布尔值。
let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]
您可以调用hasPrefix(_:)方法来计算话剧中第一幕的场景数:
var act1SceneCount = 0
for scene in romeoAndJuliet {
if scene.hasPrefix("Act 1 ") {
++act1SceneCount
}
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印输出 "There are 5 scenes in Act 1"
相似地,您可以用hasSuffix(_:)方法来计算发生在不同地方的场景数:
var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
if scene.hasSuffix("Capulet's mansion") {
++mansionCount
} else if scene.hasSuffix("Friar Lawrence's cell") {
++cellCount
}
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印输出 "6 mansion scenes; 2 cell scenes
集合类型 (Collection Types)
- 数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。
- 使用布尔值属性
isEmpty
作为检查数组count
属性的值是否为 0 的捷径。
可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量不一样。“下面的例子把"Chocolate Spread","Cheese",和"Butter"替换为"Bananas"和 "Apples":
var shoppingList : [String] = ["Eggs","Milk","Flour","Baking Powder","Chocolate Spread", "Cheese", "Butter"]
shoppingList[4...6] = ["Bananas", "Apples"]
调用数组的insert(_:atIndex:)
方法来在某个具体索引值之前添加数据项:
shoppingList.insert("Maple Syrup", atIndex: 0)
// shoppingList 现在有7项
// "Maple Syrup" 现在是这个列表中的第一项”
使用removeAtIndex(_:)
方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项:
let mapleSyrup = shoppingList.removeAtIndex(0)
// 索引值为0的数据项被移除
// shoppingList 现在只有6项,而且不包括 Maple Syrup
// mapleSyrup 常量的值等于被移除数据项的值 "Maple Syrup
如果我们只想把数组中的最后一项移除,可以使用removeLast()
方法而不是removeAtIndex(_:)
方法来避免我们需要获取数组的count
属性。
如果我们同时需要每个数据项的值和索引值,可以使用enumerate()
方法来进行数组遍历。enumerate()
返回一个由每一个数据项索引值和数据值组成的元组:
for (index, value) in shoppingList.enumerate() {
print("Item (String(index + 1)): (value)")
} - 使用
contains(_:)
方法去检查Set
中是否包含一个特定的值。
Swift 的Set
类型没有确定的顺序,为了按照特定顺序来遍历一个Set中
的值可以使用sort()
方法,它将根据提供的序列返回一个有序集合:
let favoriteGenres: Set = ["Jazz","Classical","Hip hop"]
for genre in favoriteGenres.sort() {
print("(genre)")
}
// prints "Classical"
// prints "Hip hop"
// prints "Jazz”
使用
intersect(_:)
方法根据两个集合中都包含的值创建的一个新的集合。使用
exclusiveOr(_:)
方法根据在一个集合中但不在两个集合中的值创建一个新的集合。使用
union(_:)
方法根据两个集合的值创建一个新的集合。使用
subtract(_:)
方法根据不在该集合中的值创建一个新的集合。
-
字典的
updateValue(_:forKey:)
方法可以设置或者更新特定键对应的值,并且返回更新值之前的原值。
removeValueForKey(_:)
方法用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回nil。
如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受Array
实例的 API 的参数,可以直接使用keys
或者values
属性构造一个新数组。
控制流(Control Flow)
-
For 循环
如果你不需要知道区间序列内每一项的值,你可以使用下划线(_
)替代变量名来忽略对值的访问:
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
-
条件语句
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在下面例子中,点(0, 0)可以匹配所有四个 case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配case (0, 0)
,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉:
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("((somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, (somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("((somePoint.0), (somePoint.1)) is inside the box")
default:
print("((somePoint.0), (somePoint.1)) is outside of the box")
}
// 输出 "(1, 1) is inside the box
case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为值绑定(value binding)。下面的例子展示了如何在一个(Int, Int)
类型的元组中使用值绑定来分类下图中的点(x, y):
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
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
-
控制转移语句
continue
语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
break
语句会立刻结束整个控制流的执行。当你想要更早的结束一个switch
代码块或者一个循环体时,你都可以使用break
语句。
在 Swift 中,你可以在循环体和switch
代码块中嵌套循环体和switch
代码块来创造复杂的控制流结构。然而,循环体和switch
代码块两者都可以使用break
语句来提前结束整个方法体。因此,显式地指明break
语句想要终止的是哪个循环体或者switch
代码块,会很有用。类似地,如果你有许多嵌套的循环体,显式指明continue
语句想要影响哪一个循环体也会非常有用。为了实现这个目的,你可以使用标签来标记一个循环体或者switch
代码块,当使用break
或者continue
时,带上这个标签,可以控制该标签代表对象的中断或者执行。
label name: while condition {
statements
}
下面的例子是在一个带有标签的while
循环体中调用break
和continue
语句:
let finalSquare = 25
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
gameLoop: while square != finalSquare {
if ++diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 到达最后一个方块,游戏结束
break gameLoop
case let newSquare where newSquare > finalSquare:
// 超出最后一个方块,再掷一次骰子
continue gameLoop
default:
// 本次移动有效
square += diceRoll
square += board[square]
}
}
print("Game over!")
如果骰子数刚好使玩家移动到最终的方格里,游戏结束。break gameLoop
语句跳转控制去执行while
循环体后的第一行代码,游戏结束。
如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。continue gameLoop
语句结束本次while
循环的迭代,开始下一次循环迭代。
注意:
如果上述的break
语句没有使用gameLoop
标签,那么它将会中断switch
代码块而不是while
循环体。使用gameLoop
标签清晰的表明了break
想要中断的是哪个代码块。 同时请注意,当调用continue gameLoop
去跳转到下一次循环迭代时,这里使用gameLoop
标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以continue
语句会影响到哪个循环体是没有歧义的。然而,continue
语句使用gameLoop标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的
break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
-
提前退出
像if
语句一样,guard
的执行取决于一个表达式的布尔值。我们可以使用guard
语句来要求条件必须为真时,以执行guard
语句后的代码。不同于if
语句,一个guard
语句总是有一个else
分句,如果条件不为真则执行else
分句中的代码。
-
检测API可用性
if #available(iOS 10, macOS 10.12, *) { // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API } else { // 使用先前版本的 iOS 和 macOS 的 API }
以上可用性条件指定,在iOS中,if
语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中,仅在 macOS 10.12 及更高才会运行。最后一个参数,*
,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。
函数(Functions)
- 可选元组类型如
(Int, Int)?
与元组包含可选类型如(Int?, Int?)
是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。 -
函数参数名称
可以在局部参数名前指定外部参数名,中间以空格分隔。
如果你不想为第二个及后续的参数设置外部参数名,用一个下划线(_
)代替一个明确的参数名。因为第一个参数默认忽略其外部参数名称,显式地写下划线是多余的。
一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(...
)的方式来定义可变参数:
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)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
注意:
一个函数最多只能有一个可变参数。
通过在参数名前加关键字var
来定义变量参数。
如果想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。定义一个输入输出参数时,在参数定义前加inout
关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。你只能传递变量给输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 &
符,表示这个值可以被函数修改。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3
注意:
函数是独立的,而方法是函数封装在类,结构或者枚举中的函数。
闭包(Closures)
闭包表达式语法有如下一般形式:
{ (parameters) -> returnType in
statements
}
闭包可以省略它的参数的type 和返回值的type. 如果省略了参数和参数类型,就也要省略in
关键字。 如果被省略的type 无法被编译器获知(inferred) ,那么就会抛出编译错误。
闭包可以省略参数,转而在方法体(statement)中使用 $0, $1, $2 来引用出现的第一个,第二个,第三个参数。
如果闭包中只包含了一个表达式,那么该表达式就会自动成为该闭包的返回值。
函数:
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func backwards(s1: String, s2: String) -> Bool {
return s1 > s2
}
var reversed = names.sort(backwards)
// reversed 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
闭包:
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )-
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}// 以下是不使用尾随闭包进行函数调用 someFunctionThatTakesAClosure({ // 闭包主体部分 }) // 以下是使用尾随闭包进行函数调用 someFunctionThatTakesAClosure() { // 闭包主体部分 }
枚举(Enumerations)
- 使用enum关键词来创建枚举并且把它们的整个定义放在一对大括号内:
enum SomeEnumeration {
// 枚举定义放在这里
}
下面是用枚举表示指南针四个方向的例子:
enum CompassPoint {
case North
case South
case East
case West
} - 如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个let或者var。
类和结构体(Classes and Structures)
-
结构体和枚举是值类型
值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。
struct Resolution {
var width = 0
var height = 0
}
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
// 这里,将会显示cinema的width属性确已改为了2048:
print("cinema is now (cinema.width) pixels wide")
// 输出 "cinema is now 2048 pixels wide
print("hd is still (hd.width) pixels wide")
// 输出 "hd is still 1920 pixels wide
cinema
的width
属性确已改为了2048,然而,初始的hd
实例中width
属性还是1920。
-
类是引用类型
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let tenEighty = VideoMode()
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now (tenEighty.frameRate)")
// 输出 "The frameRate property of theEighty is now 30.0
因为类是引用类型,所以tenEight
和alsoTenEight
实际上引用的是相同的VideoMode
实例。换句话说,它们是同一个实例的两种叫法。通过查看tenEighty
的frameRate
属性,我们会发现它正确的显示了所引用的VideoMode
实例的新帧率,其值为30.0。
需要注意的是tenEighty
和alsoTenEighty
被声明为常量而不是变量。然而你依然可以改变tenEighty.frameRat
e和alsoTenEighty.frameRate
,因为tenEighty
和alsoTenEighty
这两个常量的值并未改变。它们并不“存储”这个VideoMode
实例,而仅仅是对VideoMod
e实例的引用。所以,改变的是被引用的VideoMode
的frameRate
属性,而不是引用VideoMode
的常量的值。
- 如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:
等价于(===
)
不等价于(!==
) - Swift 中,许多基本类型,诸如
String
,Array
和Dictionary
类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。
Objective-C 中NSString
,NSArray
和NSDictionary
类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
属性 (Properties)
- 结构体(struct)属于值类型。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
属于引用类型的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。 -
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用
lazy
来标示一个延迟存储属性,只有var
才能用lazy
初始化 -
存储属性
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。
-
计算属性
除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个getter
和一个可选的 setter
,来间接获取和设置其他属性或变量的值。
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)
如果计算属性的 setter
没有定义表示新值的参数名,则可以使用默认名称newValue
。
必须使用var
关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let
关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只有 getter 没有 setter 的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。“只读计算属性的声明可以去掉get
关键字和花括号。
- 属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。属性重写请参考重写。
注意:
不需要为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。
可以为属性添加如下的一个或全部观察器:
1.willSet在新的值被设置之前调用
2.didSet在新的值被设置之后立即调用
willSet
观察器会将新的属性值作为常量参数传入,在willSet
的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称newValue
表示。类似地,didSet
观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名oldValue
。
注意:
如果在一个属性的didSet
观察器里为它赋值,这个值会替换该观察器之前设置的值。
-
类型属性
使用关键字static
来定义类型属性。类型属性用于定义特定类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
方法(Methods)
-
实例方法
Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(_
)作为该参数的显式外部名称,这样做将覆盖默认行为。
结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。但是,如果需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。方法还可以给它隐含的self
属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。要使用变异方法, 将关键字mutating
放到方法的func
关键字之前就可以了:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at ((somePoint.x), (somePoint.y))")
// 打印输出: "The point is now at (3.0, 4.0)
可变方法能够赋给隐含属性self一个全新的实例。上面Point的例子可以用下面的方式改写:
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)
}
}
-
类型方法
实例方法是被类型的某个实例调用的方法。你也可以定义类型本身调用的方法,这种方法就叫做类型方法。声明结构体和枚举的类型方法,在方法的func
关键字之前加上关键字static
。类可能会用关键字class
来允许子类重写父类的实现方法。
下标脚本(Subscripts)
-
下标脚本语法
subscript(index: Int) -> Int { get { // 返回与入参匹配的Int类型的值 } set(newValue) { // 执行赋值操作 } }
newValue
的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明newValue
就算不写,在set代码块中依然可以使用默认的newValue
这个变量来访问新赋的值。
与只读计算型属性一样,可以直接将原本应该写在get代码块中的代码写在subscript中:
subscript(index: Int) -> Int {
// 返回与入参匹配的Int类型的值
}
下面代码演示了一个在TimesTable结构体中使用只读下标脚本的用法,该结构体用来展示传入整数的n倍:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("3的6倍是(threeTimesTable[6])")
// 输出 "3的6倍是18”
继承(Inheritance)
- Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
- 如果要重写某个特性,你需要在重写定义的前面加上
override
关键字。重写一个属性时,必需将它的名字和类型都写出来。不可以将一个继承来的读写属性重写为一个只读属性。你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供getter
和setter
即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
在合适的地方,你可以通过使用super
前缀来访问超类版本的方法,属性或下标脚本。
注意:
如果你在重写属性中提供了setter
,那么你也一定要提供getter
。如果你不想在重写版本中的getter
里修改继承来的属性值,你可以直接通过super.someProperty
来返回继承来的值,其中someProperty
是你要重写的属性的名字
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供willSet
或didSet
实现是不恰当。此外还要注意,你不可以同时提供重写的setter
和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的setter
,那么你在setter
中就可以观察到任何值变化了。
- 可以通过把方法,属性或下标脚本标记为
final
来防止它们被重写,只需要在声明关键字前加上final
特性即可。(例如:final var
,final func
,final class func
, 以及final subscript
)
你可以通过在关键字class
前添加final
特性(final class
)来将整个类标记为 final 的,这样的类是不可被继承的。
构造过程
- 如果你在定义构造器时没有提供参数的外部名字,Swift 会为构造器的每个参数自动生成一个跟内部名字相同的外部名。只要构造器定义了某个外部参数名,你就必须使用它,忽略它将导致编译错误。
如果不希望为构造器的某个参数提供外部名字,你可以使用下划线(_
)来显示描述它的外部名。 -
指定构造器和便利构造器
便利构造器(convenience initializers)是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。
指定构造器必须总是向上代理,便利构造器必须总是横向代理。
-
构造器的继承和重写
当你写一个父类中带有指定构造器的子类构造器时,你需要重写这个指定的构造器。因此,你必须在定义子类构造器时带上override
修饰符。
构造器的自动继承 子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且可以在安全的情况下以最小的代价继承父类的构造器。假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:
规则 1:如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
规则 2:如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承所有父类的便利构造器。即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
- 如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(
init?
)。
注意: 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其类型相同。
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。通过return nil
语句,来表明可失败构造器在何种情况下“失败”。
类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。下面例子展示了如何使用隐式解析可选类型来实现这个类的可失败构造器的要求:
class Product {
let name: String!
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}
上面定义的Product
类有一个不能为空字符串的name
常量属性。为了强制满足这个要求,Product
类使用了可失败构造器来确保这个属性的值在构造器成功时不为空。毕竟,Product
是一个类而不是结构体,Product
类的所有可失败构造器必须在自己失败前给name
属性一个初始值。上面的例子中,Product
类的name
属性被定义为隐式解析可选字符串类型(String!
),因为它是一个可选类型,所以在构造过程里的赋值前,name
属性有个默认值nil
。用默认值nil
意味着Product
类的所有属性都有一个合法的初始值。因而,在构造器中给name
属性赋一个特定的值前,可失败构造器能够在传入一个空字符串时触发构造过程的失败。
可以用一个非可失败构造器重写一个可失败构造器,但反过来却行不通。
-
必要构造器
在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 在这里添加该必要构造器的实现代码
}
}
在子类重写父类的必要构造器时,必须在子类的构造器前也添加required
修饰符,这是为了保证继承链上子类的构造器也是必要构造器。在重写父类的必要构造器时,不需要添加override
修饰符。
-
通过闭包和函数来设置属性的默认值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。下面列举了闭包如何提供默认值的代码概要:
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意:
如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
析构过程(Reinitialization)
- 析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字
deinit
来标示,类似于构造器要用init
来标示。
自动引用计数(Automatic Reference Counting)
- Swift 提供了两种办法用来解决在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
- 声明属性或者变量时,在前面加上
weak
关键字表明这是一个弱引用。弱引用必须被声明为变量,表明其值能在运行时被修改。因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。 - 和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。你可以在声明属性或者变量时,在前面加上关键字
unowned
表示这是一个无主引用。使用无主引用,你必须确保引用始终指向一个未销毁的实例。 - 两个属性的值都允许为
nil
,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
一个属性的值允许为nil
,而另一个属性的值不允许为nil
,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为nil
。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。 -
闭包引起的循环强引用
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}
可空链式调用(Optional Chaining)
- 可空链式调用(Optional Chaining)是一种可以请求和调用属性、方法及下标的过程,它的可空性体现于请求或调用的目标当前可能为空(nil)。如果可空的目标有值,那么调用就会成功;如果选择的目标为空(nil),那么这种调用将返回空(nil)。多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为空(nil)将导致整个链调用失败。
注意: Swift 的可空链式调用和 Objective-C 中的消息为空有些相像,但是 Swift 可以使用在任意类型中,并且能够检查调用是否成功。
-
使用可空链式调用来强制展开
通过在想调用非空的属性、方法、或下标的可空值(optional value)后面放一个问号,可以定义一个可空链。这一点很像在可空值后面放一个叹号(!)来强制展开其中值。它们的主要的区别在于当可空值为空时可空链式只是调用失败,然而强制展开将会触发运行时错误。
错误处理
-
表示并抛出错误
在Swift中,错误用遵循ErrorType
协议类型的值来表示。这个空协议表示一种可以用做错误处理的类型。 Swift的枚举类型尤为适合塑造一组相关的错误情形(error conditions),枚举的关联值(assiciated values)还可以提供额外信息,表示某些错误情形的性质。比如你可以这样表示在一个游戏中操作自动贩卖机会出现的错误情形:
enum VendingMachineError: ErrorType {
case InvalidSelection //选择无效
case InsufficientFunds(coinsNeeded: Int) //金额不足
case OutOfStock //缺货
}
-
处理错误
某个错误被抛出时,那个地方的某部分代码必须要负责处理这个错误 - 比如纠正这个问题、尝试另外一种方式、或是给用户提示这个错误。 Swift中有 4 种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用do-catch语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。
- 用
throws
关键字标来识一个可抛出错误的函数,方法或是构造器。在函数声明中的参数列表之后加上throws
。一个标识了throws
的函数被称作throwing函数。如果这个函数还有返回值类型,throws
关键词需要写在箭头(->)的前面:
func canThrowErrors() throws -> String
调用可抛出错误函数时在它前面加上try
关键字。
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
} - 可以使用一个
do-catch
语句运行一段闭包代码来做错误处理。如果在do
语句中的代码抛出了一个错误,则这个错误会与catch
语句做匹配来决定哪条语句能处理它。 下面是do-catch语句的通用形式:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} -
将错误作为可选类型处理
可以使用try?
通过将其转换成一个可选值来处理错误。如果在评估try?
表达式时一个错误被抛出,那么这个表达式的值就是nil
。例如下面代码中的x
和y
有相同的值和特性:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
如果someThrowingFunction()
抛出一个错误,x
和y
的值是nil
。否则x
和y
的值就是该函数的返回值。
-
指定清理操作
可以使用defer
语句在代码执行到要离开当前的代码段之前去执行一套语句。该语句让你能够做一些应该被执行的必要清理工作。defer
语句将代码的执行延迟到当前的作用域退出之前。该语句由 defer
关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break
或是 return
语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条 defer
语句中的代码会在第二条defer
语句中的代码被执行之后才执行,以此类推。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}
上面的代码使用一条defer
语句来确保 open(_:)
函数有一个相应的对 close(_:)
函数的调用。
:注意:
即使没有涉及到错误处理,你也可以使用defer
语句。
类型转换(Type Casting)
- 用类型检查操作符(
is
)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true
,否则返回false
。 -
向下转型
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(as?
或as!
)
当你不确定向下转型可以成功时,用类型转换的条件形式(as?
)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。这使你能够检查向下转型是否成功。
只有你可以确定向下转型一定会成功时,才使用强制形式(as!
)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运
行时错误。
-
AnyObject
可以代表任何class
类型的实例。
Any
可以表示任何类型,包括方法类型(function types)。
扩展(Extensions)
-
扩展语法
声明一个扩展使用关键字extension
:
extension SomeType {
// 加到SomeType的新功能写到这里
}
一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议(protocol)。当这种情况发生时,协议的名字应该完全按照类或结构体的名字的方式进行书写:
extension SomeType: SomeProtocol, AnotherProctocol {
// 协议实现写到这里
}
- 扩展可以向已有类型添加计算型实例属性和计算型类型属性。
- 扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。
扩展能向类中添加新的便利构造器,但是它们不能向类中添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。 - 扩展可以向已有类型添加新的实例方法和类型方法。
- 扩展可以向一个已有类型添加新下标。
- 扩展可以向已有的类、结构体和枚举添加新的嵌套类型。
协议(Protocols)
-
协议的语法
协议的定义方式与类,结构体,枚举的定义非常相似:
protocol SomeProtocol {
// 协议内容
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
- 协议中的通常用
var
来声明变量属性,在类型声明后加上{ set get }
来表示属性是可读可写的,只读属性则用{ get }
来表示。
在协议中定义类属性(type property)时,总是使用static
关键字作为前缀。当协议的遵循者是类时,可以使用class
或static
关键字来声明类属性。 - 在协议中定义类方法的时候,总是使用
static
关键字作为前缀。当协议的遵循者是类的时候,你可以在类的实现中使用class
或者static
来实现类方法。 - 有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将
mutating
关键字作为函数的前缀,写在func
之前,表示可以在该方法中修改它所属的实例及其实例属性的值。 - 协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol {
init(someParameter: Int)
}
如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required
和override
修饰符。 -
通过扩展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named (name)"
}
}
extension Hamster: TextRepresentable {}
从现在起,Hamster的实例可以作为TextRepresentable类型使用:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 输出 "A hamster named Simon
-
类专属协议
你可以在协议的继承列表中,通过添加class
关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该class
关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 协议定义
}
在以上例子中,协议SomeClassOnlyProtocol
只能被类(class)类型适配。如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。
-
检验协议的一致性
你可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同(详情查看类型转换):
is
操作符用来检查实例是否遵循了某个协议。
as?
返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
。
as
用以强制向下转型,如果强转失败,会引起运行时错误。
-
可选协议只能在含有
@objc
前缀的协议中生效。 这个前缀表示协议将暴露给Objective-C代码,详情参见Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上@obj
前缀。 还需要注意的是,@objc
的协议只能由继承自 Objective-C 类的类或者其他的@objc类来遵循。它也不能被结构体和枚举遵循。
下面的例子定义了一个叫Counter
的整数加法类,它使用外部的数据源来提供每次的增量。数据源是两个可选规定,在CounterDataSource
协议中定义:
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
} -
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用where
关键字来描述限制情况。(Where语句)。:
例如,你可以扩展CollectionType
协议,但是只适用于元素遵循TextRepresentable
的情况:
extension CollectionType where Generator.Element : TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
}
}
泛型(Generics)
- 泛型代码可以让你写出根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型。它的可以让你避免重复的代码,用一种清晰和抽象的方式来表达代码的意图。
- 泛型函数可以工作于任何类型,这里用于交换两个值:
func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}
//只交换int
func swapTwoInts(inout a: Int, inout _ b: Int)
//交换相同的类型
func swapTwoValues<T>(inout a: T, inout _ b: T)
这个函数的泛型版本使用了占位类型名字(通常此情况下用字母T来表示)来代替实际类型名(如Int、String或Double)。 - 当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表(例如
<T>
)。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的。 -
类型约束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // 这里是函数主体 }
上面这个假定函数有两个类型参数。第一个类型参数T
,有一个需要T
必须是SomeClass
子类的类型约束;第二个类型参数U
,有一个需要U
必须遵循SomeProtocol
协议的类型约束。
-
关联类型(Associated Types)
当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为typealias
关键字。
访问控制(Access Control)
- 访问控制可以限定其他源文件或模块中代码对你代码的访问级别。这个特性可以让我们隐藏功能实现的一些细节,并且可以明确的申明我们提供给其他人的接口中哪些部分是他们可以访问和使用的。
-
访问级别
“Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
public
:可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。通常情况下,Framework
中的某个接口是可以被任何人使用时,你可以将其设置为public
级别。
internal
:可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。通常情况下,某个接口或Framework
作为内部结构使用时,你可以将其设置为internal
级别。
private
:只能在当前源文件中使用的实体,称为私有实体。使用private
级别,可以用作隐藏某些功能的实现细节。
除非有特殊的说明,否则实体都使用默认的访问级别internal
。
public class SomePublicClass { // 显式的 public 类
public var somePublicProperty = 0 // 显式的 public 类成员
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
class SomeInternalClass { // 隐式的 internal 类
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
private class SomePrivateClass { // 显式的 private 类
var somePrivateProperty = 0 // 隐式的 private 类成员
func somePrivateMethod() {} // 隐式的 private 类成员
}
元组的访问级别与元组中访问级别最低的类型一致。
-
子类的访问级别不得高于父类的访问级别。如果我们无法直接访问某个类中的属性或函数等,那么可以继承该类,从而可以更容易的访问到该类的类成员:
public class A {
private func someMethod() {}
}internal class B: A { override internal func someMethod() {} }
常量、变量、属性不能拥有比它们的类型更高的访问级别。
元组的访问级别与元组中访问级别最低的类型一致 。如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。
如果定义了一个新的协议,并且该协议继承了一个已知的协议,那么新协议拥有的访问级别最高也只和被继承协议的访问级别相同。