The Swift Programming Language

Swift 4.2

Swift 初见(A Swift Tour)
let 来声明常量,使用 var 来声明变量
不用明确地声明类型,声明的同时赋值的话,编译器 会自动推断类型。
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换

使用方括号 "[]"来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]

要创建一个空数组或者字典,使用初始化语法。
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
如果类型信息可以被推断出来,你可以用 [] 和 [:] 来创建空数组和空字典——就像你声明变量或者给函数传参 数的时候一样。
shoppingList = []
occupations = [:]

包裹条件和循环的变量括号可以省略,但是语句体的大括号是必须的

在 if 语句中,条件必须是一个布尔表达式——这意味着像 if score { ... } 这样的代码将报错,而不会隐形地 与 0 做对比。
(1)可以一起使用 iflet来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是nil以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
如果变量的可选值是nil ,条件会判断为false,大括号中的代码会被跳过。如果不是 ,会将值解包并赋给 后面的常量,这样代码块中就可以使用这个值了。
(2)另一种处理可选值的方法是通过使用"??"操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替

switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
运行 switch 中匹配到的子句之后,程序会退出switch语句,并不会继续向下运行,所以不需要在每个子句结尾写break 

使用 ..< 创建的范围不包含上界,如果想包含的话需要使用 ... 

默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 _ 表示不使用参数标签。
func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person:"Bob", day: "Tuesday")

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

使用元组来让一个函数返回多个值。
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) 

函数可以带有可变个数的参数,这些参数在函数内表现为数组的形式
func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
}
return sum }
sumOf()
sumOf(numbers: 42, 597, 12)

函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
func makeIncrementer() -> ((Int) -> Int) 
函数也可以当做参数传入另一个函数。
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool 

使用 {} 来创建 一个匿名闭包。使用"in"将参数和返回值类型声明与闭包函数体进行分离。
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
})

有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型
和返回值。单个语句闭包会把它语句的值当做结果返回。
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)

使用 init 来创建一个构造器
使用 deinit 创建一个析构函数
创建类的时候并不需要一个标准的根类,所以你可以忽略父类。
子类如果要重写父类的方法的话,需要用 override 标记——如果没有添加 override 就重写父类方法的话编译器会报错。编译器同样会检测override标记的方法是否确实在父类中。

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
}
 var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
} }
    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
} }
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
在 perimeter 的 setter中,新值的名字是newValue。你可以在set之后显式的设置一个名字。

处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ? 。如果 ? 之前的值是 nil , ? 后面的东西都会被忽略,并且整个表达式返回 nil 。否则, ? 之后的东西都会被运行。

使用 enum 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
默认情况下,Swift 按照从0开始每次加1的方式为原始值进行赋值,不过你可以通过显式赋值进行改变

***结构体和类它们之间最大的一个区别就 是结构体是传值,类是传引用。***

使用 protocol 来声明一个协议
protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}
类、枚举和结构体都可以实现协议。

"注意声明" struct 时候 mutating 关键字用来标记一个会修改结构体的方法。 class 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。

使用 extension 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展在别处修改定义,甚至是从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。
extension Int: ExampleProtocol 

使用采用 Error 协议的类型来表示错误。
enum PrinterError: Error {
    case OutOfPaper
    case NoToner
    case OnFire 
}
(1)**使用 throw 来抛出一个错误并使用throws来表示一个可以抛出错误的函数**。如果在函数中抛出一个错误,这个函 数会立刻返回并且调用该函数的代码会进行错误处理。
func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

(2)一种方式是使用do-catch。在do代码块中,使用try 来标记可以抛出错误 的代码。在catch代码块中,除非你另外命名,否则错误会自动命名为 error 。
do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
可以使用多个 catch 块来处理特定的错误。参照 switch 中的 case 风格来写 catch 。
(3)另一种处理错误的方式使用try?将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 nil 。否则的话,结果会是一个包含函数返回值的可选值。
 let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")

使用 defer 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。
使用defer,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执 行时机截然不同。

在尖括号里写一个名字来创建一个泛型函数或者类型。
可以创建泛型函数、方法、类、枚举和结构体
在类型名后面使用where来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Iterator.Element:Equatable,T.Iterator.Element==U.Iterator.Element
复制代码
Swift教程
"基础部分:"
当你声明常量或者变量的时候可以加上类型标注(type annotation)
var welcomeMessage: String

你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
var red, green, blue: Double

如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方 式将其作为名字使用。

print(_:separator:terminator:)
separator 和 terminator参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。

// 这是一个注释
/* 这是一个, 多行注释 */
Swift 的多行注释可以嵌套在其它的多行注释之中
/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */ 
这是第一个多行注释的结尾 */

与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号( ; )

如果你没有给浮点字面量标明类型,Swift 会推断你想要的是Double 
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 4.75 会变成 4 , -3.9 会变成 -3 。

类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用typealias关键字来定义类型别名。
typealias AudioSample = UInt16

元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
(404, "Not Found"),这个元组可以被描述为“一个类型为(Int, String)的元组”

如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线( _ )标记
let (justTheStatusCode, _) = http404Error

还可以通过下标来访问元组中的单个元素,下标从零开始:
print("The status code is \(http404Error.0)")

可以在定义元组的时候给单个元素命名:
let http200Status = (statusCode: 200, description: "OK")
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
print("The status code is \(http200Status.statusCode)")


nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil 

Swift 的 nil 和 Objective-C中的nil并不一样。在Objective-C中,nil是一个指向不存在对象的指针。
在 Swift中,nil不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设 置为 nil ,不只是对象类型。

当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号( ! )来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的强制解析(forced unwrapping):
使用 ! 来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一 个非 nil 的值。


使用可选绑定(optionalbinding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。
可选绑定可以用在ifwhile语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将 可选类型中的值赋给一个常量或者变量。
if let constantName = someOptional {
    statements
}

有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型总会有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型 的后面的问号( String? )改成感叹号( String! )来声明一个隐式解析可选类型。

当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型 主要被用在 Swift 中类的构造过程中
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用 解析来获取可选值。

可选类型 String 和隐式解析可选类型 String 之间的区别:
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感叹号来获取值

let assumedString: String! = "An implicitly unwrapped optional string." 
let implicitString: String = assumedString // 不需要感叹号

如果一个变量之后可能变成nil的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否 是 nil 的话,请使用普通可选类型。
-----------------------------------------------------------------------------
"基本运算符"

溢出运算符来实现溢出运算(如 a &+ b )

在对负数 b 求余时, b 的符号会被忽略。这意味着 a % b 和 a %-b的结果是相同的。
-9 % 4 // 等于 -1
-9 = (4 × -2) + -1 //余数是 -1

空合运算符(Nil Coalescing Operator)
空合运算符( a ?? b )将对可选类型a进行空判断,如果a包含一个值就进行解封,否则就返回一个默认值 b 。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。 空合运算符是对以下代码的简短表达方法:
a != nil ? a! : b

闭区间运算符( a...b )定义一个包含从 a 到 b (包括 a 和 b )的所有值的区间。
半开区间运算符( a..<b )定义一个从 a 到 b 但不包括 b 的区间。
-----------------------------------------------------------------------------
"字符串和字符"

字符串是例如 "hello,world""albatross"这样的有序的Character(字符)类型的值的集合。通过 String 类型来表示。一个String的内容可以用许多方式读取,包括作为一个 Character 值的集合。

var emptyString = "" // 空字符串字面量 
var anotherEmptyString = String() // 初始化方法

Swift 的 String 类型是值类型。

通过 for-in 循环来遍历字符串中的 characters 属性来获取每一个字符的值:
for character in "Dog!?".characters 

通过标明一个 Character类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
let exclamationMark: Character = "!"

字符串可以通过传递一个值类型为 Character 的数组作为自变量来初始化
let catCharacters: [Character] = ["C", "a", "t", "!", "?"] 
let catString = String(catCharacters)

可扩展的字符群集可以组成一个或者多个Unicode标量。这意味着不同的字符以及相同字符的不同表示方式可 能需要不同数量的内存空间来存储。所以Swift中的字符在一个字符串中并不一定占用相同的内存空间数量。

Swift 语言提供 Arrays、Sets和Dictionaries三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。
***Swift 的 Arrays 、 Sets 和 Dictionaries 类型被实现为泛型集合。***

var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]

如果我们同时需要每个数据项的值和索引值,可以使用enumerated()方法来进行数组遍历
for (index, value) in shoppingList.enumerated()

Hashable 协议符合 Equatable协议,所以遵循该协议的类型也必须提供一个"是否相等"运算符( == )的实 现。这个 Equatable 协议要求任何符合 == 实现的实例间都是一种相等的关系。也就是说,对于 a,b,c 三个值来 说, == 的实现必须满足下面三种情况:
• a == a (自反性)
• a == b 意味着 b == a (对称性)
• a == b && b == c 意味着 a == c (传递性)

一个字典的 Key 类型必须遵循 Hashable 协议,就像 Set 的值类型。

访问
for (airportCode, airportName) in airports 
for airportCode in airports.keys 
for airportName in airports.values
-----------------------------------------------------------------------------
"控制流:"


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

默认( default )分支来涵盖其它所有没有对应的值,这个默认分支必须在 switch 语句的最后面。

不存在隐式的贯穿
当匹配的 case 分支中的代码执行完毕后,程序会终止switch语句,而不会继续执行下一个 case分支。这也就是说,不需要在 case分支中显式地使用 break 语句。
如果想要显式贯穿case分支,请使用 fallthrough 语句

使用元组在同一个 switch语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划 线( _ )来匹配所有可能的值。

 fallthrough关键字不会检查它下一个将会落入执行的case中的匹配条件。fallthrough简单地使代码继续连接到下一个 case 中的代码

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

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

尽管函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
func sayHelloWorld() -> String {
    return "hello, world"
} 
print(sayHelloWorld())

可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
func minMax(array: [Int]) -> (min: Int, max: Int)

可选元组类型如 (Int, Int)? 与元组包含可选类型如 (Int?, Int?) 是不同的.可选的元组类型,整个 元组是可选的,而不只是元组中的每个元素值。


每个函数参数都有一个参数标签( argument label )以及一个参数名称( parameter name )。
参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。
参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。

如果不希望为某个参数添加一个标签,可以使用一个下划线(_)来代替一个明确的参数标签。

可以在函数体中通过给参数赋值来为任意一个参数定义默认值(Deafult  Value)。 当默认值被定义后,调用这 个函数时可以忽略这个参数。
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) 


一个可变参数(variadic parameter)可以被传入不确定数量的输入值。通过在变量类型名后面加入(...)的方式来定义可变参数。
func arithmeticMean(_ numbers: Double...) -> Double

输入输出参数
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误
定义一个输入输出参数时,在参数定义前加inout关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。

当传入的参数作为 输入输出参数时,需要在参数名前加&符,表示这个值可以被函数修改。
func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a
a= b
b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)

func addTwoInts(_ a: Int, _ b: Int) -> Int的函数类型是(Int, Int) -> Int

func printHelloWorld()的函数类型是 () -> Void 
-----------------------------------------------------------------------------
"闭包:"


闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 Swift 会为你管理在捕获 过程中涉及到的所有内存操作。

闭包表达式语法有如下的一般形式:
{ (parameters) -> returnType in
    statements
}

(1) func backward(_ s1: String, _ s2: String) -> Bool {
    return s1 > s2
}
var reversedNames = names.sorted(by: backward)
利用闭包 表达式语法可以更好地构造一个"内联排序闭包"。
(2) reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

闭包的函数体部分由关键字"in"引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。

根据上下文推断类型
因为排序闭包函数是作为sorted(by:)方法的参数传入的,Swift可以推断其参数和返回值的类型。
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

单表达式闭包隐式返回
单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

参数名称缩写
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0$1$2 来顺序调用闭包的参数,以此类推。
reversedNames = names.sorted(by: { $0 > $1 } )

运算符方法
Swift 的 String 类型定义了关于大于 号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与sorted(by:) 方法的参数需要的函数类型相符合。
因此,可以简单地传递一个大于号,Swift可以自动推断出你想使用大于号的字符串函数实现:
reversedNames = names.sorted(by: >)

尾随闭包
如果你需要将一个很长的闭包表达式作为"最后一个参数传递给函数",可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
在使用尾随闭包时,不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) { 
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用 
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分 
})
// 以下是使用尾随闭包进行函数调用 
someFunctionThatTakesAClosure() {
// 闭包主体部分
}

sorted(by:) 方法参数的字符串排序闭包可以改写为:
reversedNames = names.sorted() { $0 > $1 }
"如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉:"
reversedNames = names.sorted { $0 > $1 }

值捕获
闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

常量指向的闭包仍然可以增加其捕获的变量的值。这是因为"函数和闭包都是引用类型"。

逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个"闭包在函数返回之后才被执行",我们称该闭包从函数中逃逸。
定义接受闭包作为参数的函数时,你可以在参数名之前标注@escaping,用来指明这个闭包是允许“逃逸”出 这个函数的。

一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。
举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。
在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

将一个闭包标记为 @escaping 意味着你必须在闭包中显式地引用 self 
someFunctionWithEscapingClosure { self.x = 100 }

自动闭包
自动闭包是一种自动创建的闭包,用于"包装传递给函数作为参数的表达式"。
这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。
这种便利语法让你"能够省略闭包的花括号",用一个普通的表达式来代替显式的闭包。
"自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。"如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行
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"
customerProvider的类型不是String,而是()->String,一个没有参数且返回值为String的函数。
----
将闭包作为参数传递给函数时,能获得同样的延时求值行为。
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"

将参数标记为 @autoclosure 来接收一个自动闭包
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0)) 
// 打印 "Now serving Ewa!"
-----------------------------------------------------------------------------
"枚举:"
enum CompassPoint {
    case north
    case south
    case east
    case west
}

每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如 CompassPoint 和 Planet)应该以一个大写字母开头。
给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:

var directionToHead = CompassPoint.west
directionToHead 的类型可以在它被 CompassPoint 的某个值初始化时推断出来。一旦 directionToHead 被声明为CompassPoint类型,你可以使用更简短的点语法将其设置为另一个 CompassPoint 的值:
directionToHead = .east

"在判断一个枚举类型的值时, switch 语句必须穷举所有情况"。
如果忽略了 .west 这 种情况,上面那段代码将无法通过编译,因为它没有考虑到 CompassPoint 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。

"关联值"
有时候能够把其他类型的关联值和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}
定义一个名为 Barcode 的枚举类型,它的一个成员值是具有 (Int,Int,Int,Int) 类型关联值的 upc ,另一个成员值是具有 String 类型关联值的 qrCode 。

"原始值"
enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

原始值的隐式赋值
在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。

递归枚举
递归枚举是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 indirect 来表示该成员可递归。
enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

也可以在枚举类型开头加上 indirect 关键字来表明它的所有成员都是可递归的:
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}
-----------------------------------------------------------------------------
"类和结构体:"
class SomeClass { 
// 在这里定义类
}
struct SomeStructure {
// 在这里定义结构体 
}

生成结构体和类实例:
let someResolution = Resolution()
let someVideoMode = VideoMode()

"结构体和类都使用构造器语法来生成新的实例。"
构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如 Resolution() 或 VideoMode()。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。


"结构体类型的成员逐一构造器"
所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
let vga = Resolution(width:640, height: 480)
"与结构体不同,类实例没有默认的成员逐一构造器。"

"结构体和枚举是值类型"
值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。


"类是引用类型"
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。

"Swift 中,许多基本类型,诸如String,Array和Dictionary类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。"
-----------------------------------------------------------------------------
"属性:"

"存储属性" 
一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。


延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 "lazy" 来标示一个延迟存 储属性。

必须将延迟存储属性声明成变量(使用var关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

class DataImporter {
    /*
DataImporter 是一个负责将外部文件中的数据导入的类。 这个类的初始化会消耗不少时间。
   */
var fileName = "data.txt"
// 这里会提供数据导入功能
}
class DataManager {
lazy var importer = DataImporter() 
var data = [String]()
// 这里会提供数据管理功能
}

"计算属性"
除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可 选的 setter,来间接获取和设置其他属性或变量的值。
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)
        }
}


如果计算属性的setter没有定义表示新值的参数名,则可以使用默认名称"newValue"。
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 {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
}

"只读计算属性"
只有 getter 没有setter的计算属性就是只读计算属性。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。

只读计算属性的声明可以去掉 get 关键字和花括号:
struct Cuboid {
    var width = 0.0, height = 0.0, depth = 0.0
    var volume: Double {
        return width * height * depth
    }
}

"属性观察器"
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。

可以为属性添加如下的一个或全部观察器:
• 在新的值被设置之前调用
• 在新的值被设置之后立即调用

willSet观察器会将新的属性值作为常量参数传入,在willSet的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称"newValue"表示。
didSet观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名"oldValue"。如果 在didSet方法中再次对该属性赋值,那么新值会覆盖旧的值。
var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
}
        didSet {
            if totalSteps > oldValue  {
                print("Added \(totalSteps - oldValue) steps")
} }
}

使用关键字 static 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父类的实现进行重写。
struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1 
    }
}

class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
       return 27 
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
} }
-----------------------------------------------------------------------------
"方法"


类型的每一个实例都有一个隐含属性叫做self,self完全等同于该实例本身。你可以在一个实例的实例方法中 使用这个隐含的 self 属性来引用当前实例。

在实例方法中修改值类型
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 可变"(mutating)"
要使用可变方法,将关键字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)

类型方法
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做类型方法。
在方法的 func 关键字之前加上关键字 static ,来指定类型方法。类还可以用关键字 class 来允许子类重写父类的方法实现。

-----------------------------------------------------------------------------
"下标:"


下标可以定义在类、结构体和枚举中,是访问集合,列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。

一个类型可以定义多个下标,通过不同索引类型进行重载。

下标语法
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存。
定义下标使用 subscript 关键字,指定一个或多个输入参数和返回类型
subscript(index: Int) -> Int {
    get {
// 返回一个适当的 Int 类型的值 
    }
    set(newValue) {
// 执行适当的赋值操作
    } 
}

"如同只读计算型属性,可以省略只读下标的 get 关键字:"
subscript(index: Int) -> Int {
// 返回一个适当的 Int 类型的值
}

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"
-----------------------------------------------------------------------------
"继承"

Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。

重写
子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。我们把这种行为叫重写。
如果要重写某个特性,你需要在重写定义的前面加上override关键字
class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
} }

不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 willSet 或 didSet 实现是不恰当。

防止重写
你可以通过把方法,属性或下标标记为final来防止它们被重写,只需要在声明关键字前加上 final 修饰符即可
(例如: final var , final func , final class func ,以及 final subscript )。
-----------------------------------------------------------------------------
"构造过程"

Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。

构造器
构造器在创建某个特定类型的新实例时被调用
init() {
// 在此处执行构造过程
}

默认构造器
"如果结构体或类的所有属性都有默认值,同时没有自定义的构造器",那么 Swift 会给这些结构体或类提供一个默 认构造器(default initializers)。 
这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。

"结构体的逐一成员构造器"
除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。

"值类型的构造器代理"
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

"构造器代理的实现规则和形式在值类型和类类型中有所不同"
值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。
类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。

假如希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器 写到扩展( extension )中,而不是写在值类型的原始定义中。

对于值类型,你可以使用self.init在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内 部调用 self.init 
struct Rect {
    var origin = Point()
    var size = Size()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
} }

"类的继承和构造过程"
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

指定构造器和便利构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

便利构造器是类中比较次要的、辅助型的构造器。
可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。

类的指定构造器的写法跟值类型简单构造器一样:
init(parameters) {
    statements
}
便利构造器也采用相同样式的写法,但需要在 init 关键字之前放置 convenience 关键字,并使用空格将它们俩分 开:
convenience init(parameters) {
    statements
}

"类的构造器代理规则"
规则 1 指定构造器必须调用其直接父类的的指定构造器。
规则 2 便利构造器必须调用同类中定义的其它构造器。
规则 3 便利构造器必须最终导致一个指定构造器被调用。
一个更方便记忆的方法是:
• 指定构造器必须总是向上代理 
• 便利构造器必须总是横向代理

两段式构造过程
Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性被引入它们的类指定一个初始值。
当每个存 储型属性的初始值被确定后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步定制它们的存 储型属性。

"Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止 一个父类的简单构造器被一个更精细的子类继承,并被错误地用来创建子类的实例。"

在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因 此,你必须在定义子类构造器时带上 override 修饰符
即使你重写的是系统自动提供的默认构造器,也需要带上override 修饰符
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

"构造器的自动继承"
如果满足特定条件,父类构造器是可以被自动继承的

"假设你为子类中引入的所有新属性都提供了默认值",以下 2 个规则适用:
规则 1 :
如果子类没有定义任何指定构造器,它将自动继承所有父类的"指定构造器"。
规则 2 :
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将 自动继承所有父类的"便利构造器"。

子类可以将父类的指定构造器实现为便利构造器

可失败构造器
如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器init? 

严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用 return nil 表明可失败构造器构造失败,而不要用关键字 return 来表明构造成功。
struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

构造失败的传递
类,结构体,枚举的可失败构造器可以横向代理到类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。

"可以用非可失败构造器重写可失败构造器,但反过来却不行"

用一个非可失败构造器 init(name:) 重写了父类的可失败构造器 init?(name:) 
class Document {
var name: String?
// 该构造器创建了一个 name 属性的值为 nil 的 document 实例 init() {}
// 该构造器创建了一个 name 属性的值为非空字符串的 document 实例 init?(name: String) {
        self.name = name
        if name.isEmpty { return nil }
    }
}

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
} }
}


可失败构造器 init!
通常来说我们通过在 init 关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以通过在
init 后面添加惊叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。你可以在 init? 中代理到 init! ,反之亦然。你也可以用 init? 重写 init! ,反之亦然。你还可以用 init 代理到 init! ,不过,一旦 init! 构造失败,则会触发一个断言。

在类的构造器前添加 "required" 修饰符表明所有该类的子类都必须实现该构造器:

在子类重写父类的必要构造器时,必须在子类的构造器前也添加required修饰符,表明该构造器要求也应用于继承链后面的子类。"在重写父类中必要的指定构造器时,不需要添加 override 修饰符"
class SomeClass {
    required init() {
// 构造器的实现代
    } 
 }

class SomeSubclass: SomeClass {
    required init() {
// 构造器的实现代码 
    }
}

通过闭包或函数设置属性的默认值
如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值
class SomeClass {
    let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同
    return someValue
    }() 
}
"注意闭包结尾的大括号后面接了一对空的小括号。这用来告诉Swift立即执行此闭包"。
如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
-----------------------------------------------------------------------------
"析构过程"

析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 deinit 来标示

Swift 会自动释放不再需要的实例以释放资源,通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能 需要进行一些额外的清理。

在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数
deinit {
// 执行析构过程
}
-----------------------------------------------------------------------------
"自动引用计数器"

Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存

引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。

我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。

"可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题"

弱引用(weak reference)和无主引用(unowned reference)
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时
在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。
相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。

弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例。这个特性阻止了引用变为循环强引用。
声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。

weak var tenant: Person?
unowned let customer: Customer

"(1)Person和Apartment的例子展示了两个属性的值都允许为nil,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。"

"(2)Customer 和 CreditCard的例子展示了一个属性的值允许为nil,而另一个属性的值不允许为 nil ,这也可能会 产生循环强引用。这种场景最适合通过无主引用来解决。"

"(3)存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 nil 。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性"
var capitalCity: City!
unowned let country: Country

闭包引起的循环强引用
闭包捕获列表 (closure capture list)
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用

定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如 self )或初始化过的变量(如 delegate = self.delegate! )
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in // 这里是闭包的函数体
}

在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 无主引用
相反的,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil。这使我们可以在闭包体内检查它们是否存在。
如果被捕获的引用绝对不会变为 nil ,应该用无主引用,而不是弱引用。
-----------------------------------------------------------------------------
"可选链"

可选链式调用是一种可以在当前值可能为nil的可选值上请求和调用属性、方法及下标的方法。
如果可选值有值,那么调用就会成功;如果可选值是 nil ,那么调用将返回 nil 。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为nil,整个调用链都会失败,即返回 nil 。

可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用 可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 Int 类型,则会变为 Int? 类型。

连接多层可选链式调用
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
也就是说:
• 如果你访问的值不是可选的,可选链式调用将会返回可选值。
• 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”
-----------------------------------------------------------------------------
"错误处理"

抛出错误使用 "throw" 关键字

把函数抛出的错误传递给调用此函数的代码、用do-catch语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。

在调用一个能抛出错误的函数、方法或者构造器之前,加上 try 关键字,或者 try? 或 try! 这种变体
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 "throws"关键字。一个标有 throws 关键字的函数被称作"throwing 函数"。
如果这个函数指明了返回值类型, throws 关键词需要写在箭头( - > )的前面。
func canThrowErrors() throws -> String

用 Do-Catch 处理错误
可以使用一个 do-catch语句运行一段闭包代码来处理错误。如果在do子句中的代码抛出了一个错误,这个错误 会与 catch 子句做匹配,从而决定哪条子句能处理它。
do {
    try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
} 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.")
}

禁用错误传递
有时你知道某个函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写”try!“来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

指定清理操作
可以使用 "defer" 语句在即将离开当前代码块时执行一系列语句。
该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return或者break的语句。
例如,你可以用defer语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。

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) 会在这里被调用,即作用域的最后。 }
}
-----------------------------------------------------------------------------
"类型转换"

用类型检查操作符("is")来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true ,否则返回 false 。

向下转型 
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符"(as? 或 as!)"。

因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式as?返回一个你试图向下转成的类型的可选值。
强制形式as!把试图向下转型和强制解包(转换结果结合为一个操作)。


"Any""AnyObject" 的类型转换
Swift 为不确定类型提供了两种特殊的类型别名:
• Any 可以表示任何类型,包括函数类型。 
• AnyObject 可以表示任何类类型的实例。

使用 Any 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存 储 Any 类型的数组 things :
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
-----------------------------------------------------------------------------
"嵌套类型"

Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结 构体。

要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的{}内,而且可以根据需要定义多级嵌套。
struct BlackjackCard {
// 嵌套的 Suit 枚举 enum Suit: Character {
       case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?"
    }
}


引用嵌套类型 在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue

-----------------------------------------------------------------------------
"扩展"

Swift 中的扩展可以:
• 添加计算型属性和计算型类型属性
• 定义实例方法和类型方法
• 提供新的构造器
• 定义下标
• 定义和使用新的嵌套类型
• 使一个已有类型符合某个协议

在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的 类型拥有这些功能

extension SomeType: SomeProtocol, AnotherProctocol {
// 协议实现写到这里
}

"扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供""如果使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值",那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
-----------------------------------------------------------------------------
"协议"

protocol SomeProtocol {
// 这里是协议的定义部分
}

拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { 
// 这里是类的定义部分
}

协议总是用 var 关键字来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,可读属 性则用 { get } 来表示:
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

在协议中定义类型属性时,总是使用"static"关键字作为前缀。当类类型遵循协议时,除了 static 关键 字,还可以使用 class 关键字来声明类型属性:
protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。
protocol RandomNumberGenerator {
    func random() -> Double
}
在协议中定义类方法的时候,总是使用 static 关键字作为前缀。
protocol SomeProtocol {
    static func someTypeMethod()
}


如果在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加"mutating"关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case Off, On
    mutating func toggle() {
        switch self {
        case Off:
            self = On
        case On:
            self = Off
} }
}

在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,都必须为构造器实现标上 "required" 修饰符:
class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
// 这里是构造器的实现部分 }
}

"使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协"

如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标 注 required 和 override 修饰符:
protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
// 这里是构造器的实现部分 }
}

class SomeSubClass: SomeSuperClass, SomeProtocol { 
// 因为遵循协议,需要加上 required
// 因为继承自父类,需要加上 override
required override init() {
// 这里是构造器的实现部分 }
}

协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol { 
// 这里是协议的定义部分
}

"类类型专属协议"
你可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循 该协议。class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { 
// 这里是类类型专属协议的定义部分
}

"协议合成"
有时候需要同时遵循多个协议,你可以将多个协议采用 SomeProtocol & AnotherProtocol 这样的格式进行组合,称为 协议合成(protocol composition)。你可以罗列任意多个你想要遵循的协议,以与符号( "&" )分隔。

协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。

• is 用来检查实例是否符合某个协议,若符合则返回 true ,否则返回 false 。
• as? 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 nil 。 
• as! 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。

可选的协议要求
协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用 optional 关键字作为前缀 来定义可选要求。
可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上 @objc 属 性。标记 @objc 特性的协议只能被继承自 Objective-C 类的类或者 @objc 类遵循,其他类以及结构体和枚举 均不能遵循这种协议。


使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为tring 的方法会变成((Int)->String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
}

"协议扩展(面向协议编程)"
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。
"通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数"。

为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供 的默认实现。这些限制条件写在协议名之后,使用 "where" 子句来描述
extension CollectionType where Generator.Element: TextRepresentable 

如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实。
-----------------------------------------------------------------------------
"泛型"

泛型代码让你能够根据自定义的需求,编写出"适用于任意类型"、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图

泛型函数
泛型函数可以适用于任何类型
func swapTwoValues<T>(_ a: inout T, _ b: inout T) 

占位类型名没有指明 T 必须是什么类型,但是它指明了 a 和 b 必须是同一类型 T ,无论 T 代表什么类型。只有swapTwoValues(_:_:)函数在调用时,才能根据所传入的实际类型决定 T 所代表的类型。

请始终使用大写字母开头的驼峰命名法(例如T和MyTypeParamter)来为类型参数命名,以表明它们是占 位类型,而不是一个值。

类型约束语法
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { 
// 这里是泛型函数的函数体部分
}

"关联类型"

定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。
关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 "associatedtype" 关键字来指定关联类型。
protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
-----------------------------------------------------------------------------
"访问控制"

模块和源文件
Swift 中的访问控制模型基于模块和源文件这两个概念。

模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 "import" 关键字导入另外一个模块。

源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。
尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。

"open,public,internal,filepart,private"
开放访问
公开访问
内部访问
文件私有访问
私有访问
开放访问为最高(限制最少)访问级别,私有访问为最低(限制最多)访问级别
开放访问只作用于类类型和类的成员

• 公开访问或者其他更严访问级别的类,只能在它们定义的模块内部被继承。
• 公开访问或者其他更严访问级别的类成员,只能在它们定义的模块内部的子类中重写。
• 开放访问的类,可以在它们定义的模块中被继承,也可以在引用它们的模块中被继承。
• 开放访问的类成员,可以在它们定义的模块中子类中重写,也可以在引用它们的模块中的子类重写。
• 把一个类标记为开放,显式地表明,你认为其他模块中的代码使用此类作为父类,然后你已经设计好了你的 类的代码了

"代码中的实体显式指定访问级别,那么它们默认为 internal 级别"

"单元测试目标的访问级别"
当你的应用程序包含单元测试目标时,为了测试,测试模块需要访问应用程序模块中的代码。
默认情况下只有开 放访问或公开访问级别级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用"@testable"特性
然后在允许测试的编译设置"(Build Options -> Enable Testability)" 下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。

"访问级别基本原则"
• 一个公开访问级别的变量,其类型的访问级别不能是内部,文件私有或是私有类型的。因为无法保证变量的 类型在使用变量的地方也具有访问权限。
• 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访 问,但是它的参数类型和返回类型却不可以的情况。

子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是internal,子类的访问级别就不能是 public 。
-----------------------------------------------------------------------------
"高级运算符"

按位取反运算符( ~ )可以对一个数值的全部比特位进行取反
按位异或运算符( ^ )可以对两个数的比特位进行比较

溢出运算符
在默认情况下,当向一个整数赋予超过它容量的值时,Swift默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或着过小的数的时候提供了额外的安全性。
• 溢出加法 &+ 
• 溢出减法 &- 
• 溢出乘法 &*

"运算符函数"
类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。
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)
    }
}

前缀和后缀运算符
func 关键字之前指定 prefix 或者 postfix 修饰符:

自定义运算符
新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefix、infix 或者 postfix 修饰符
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值