Swift 120 分钟入门教程

字数 13183阅读 713

Hello Word

在屏幕上打印“Hello, world”,可以用一行代码实现:

print("Hello, world")

你不需要为了输入输出或者字符串处理导入一个单独的库,你也不需要main函数,你同样不需要在每个语句结尾写上分号。

数据类型

Swift 包含了以下基础数据类型:Int 表示整型;DoubleFloat 表示浮点型;Bool 表示布尔;String 表示字符串。Swift 还提供了三个基本的集合类型,Array 数组,Set 集合和 Dictionary 字典。

Swift 还增加了高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。

Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示“那儿有一个值,并且它等于 x ”或者“那儿没有值”。可选有点像在 Objective-C 中使用nil,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的nil指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。

非可选类型,要么赋值,要么不赋值(不赋值的话,默认不会等于nil);换句话说,非可选类型永远不能为 nil

可以在类型后面加?声明可选类型。调用时它可能有值,也可能是nil,可以用变量!的形式保证变量有值。

可以在类型后面加!声明可选类型。调用时它可能有值,也可能是nil,区别于上面,直接用变量的形式已经保证变量有值了。

你可以使用可选的(Optional)元组反映整个元组可以是 nil 的情况。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 (Int,Int)?(String,Int,Bool)?

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

nil

你可以给可选变量赋值为nil来表示它没有值

声明常量和变量

let来声明常量,用var来声明变量。

类型标注

当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。

使用 :加空格类型名在变量或常量名之后就可以完成类型注解。

注释

单行注释:

// 这是一个注释

多行注释:

/* 这是一个,
多行注释 */

多行注释可以嵌套:

/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */

类型别名

类型别名(type aliases)就是给现有类型定义另一个名字。

你可以使用typealias关键字像使用普通的赋值语句一样,将某个已经存在的类型赋值为新的名字。

typealias 是单一的,不能将整个泛型类型进行重命名。只有泛型类型的确定性得到保证后,我们才可以重命名。

基本运算符(Basic Operators)

赋值运算符 =

算术运算符,基本的四则算术运算:

  • 加法(+
  • 减法(-
  • 乘法(*
  • 除法(/

求余运算符 a % b

自增 ++ 和自减 --

一元负号 -

一元正号 +

复合赋值

组合加运算 +=

比较运算符

  • 等于(a == b
  • 不等于(a != b
  • 大于(a > b
  • 小于(a < b
  • 大于等于(a >= b
  • 小于等于(a <= b

三目运算符

原型是 问题 ? 答案1 : 答案2

空合运算符

空合运算符 a ?? b

空合并运算符是对以下代码的简短表达方法

a != nil ? a! : b

区间运算符

闭区间运算符 a...b

半开区间 a..<b

逻辑运算

  • 逻辑非(!a
  • 逻辑与(a && b
  • 逻辑或(a || b

字符串和字符

String 是有序的 Character 类型的值的集合。

字符串是值拷贝,而非引用。

集合类型 (Collection Types)

Swift 语言提供ArraysSetsDictionaries三种基本的集合类型用来存储集合数据。数组是有序数据的集,集合是无序无重复数据的集,字典是无序的键值对的集。

  1. 可以用 Array<T> 也可以用 [T] 这样的简单语法创建一个数组。

创建一个空数组

var someInts = [Int]()

创建一个带有默认值的数组

var threeDoubles = [Double](count: 3, repeatedValue:0.0)
  1. Swift中的Set类型被写为Set<T>, 这里的T表示Set中允许存储的类型,和数组不同的是,集合没有等价的简化形式。

  2. Swift 的字典使用Dictionary<Key, Value>定义,其中Key是字典中键的数据类型,Value是字典中对应于这些键所存储值的数据类型。也可以用[Key: Value]这样快捷的形式去创建一个字典类型。

控制流

for...in...for initialization; condition; increment { statements }

while condition { statements }repeat { statements } while condition

ifswitch

在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止switch语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用break语句。这使得switch语句更安全、更易用,也避免了因忘记写break语句而产生的错误。

Swift 有四种控制转移语句。

  • continue
  • break
  • fallthrough
  • return
  • throw

强制解包:可以使用 if 语句来检测一个可选类型是否包含一个特定的值,如果一个可选类型确实包含一个值,在 if 语句中它将返回 true,否则返回 false。如果你已经检测确认该值存在,那么可以使用或者输出它,在输出的时候只需要在名称后面加上感叹号(!)即可,意思是告诉编译器:我已经检测好这个值了,可以使用它了。
强制解包,是一种语法,在其它地方也可以使用,并非仅限于此。

选择绑定:选择绑定帮助确定一个可选值是不是包含了一个值,如果包含,把该值转化成一个临时常量或者变量(语法上使用 let)。选择绑定可以用在 if 或 while 语句中,用来在可选类型外部检查是否有值并提取可能的值。(条件判断,并不一定需要是 Bool 类型!)

断言

断言会在运行时判断一个逻辑条件是否为true。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为true,代码运行会继续进行;如果条件判断为false,代码执行结束,你的应用被终止。

使用全局函数 assert 来使用断言调试,assert 函数接受一个布尔表达式和一个断言失败时显示的消息。

带标签的语句

label name: while condition { statements }

guard 提前退出

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

guard condition else xxx 和其它语言里的 xxx unless condition 效果类似。

函数

没有参数名字的函数,复杂的带局部和外部参数名的函数。参数可以提供默认值,参数也可以既当做传入参数,也当做传出参数。

每个函数都有一种类型,包括函数的参数值类型和返回值类型。

函数可以有多个输入参数,写在圆括号中,用逗号分隔。

函数可以没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。

函数可以没有返回值。

函数的定义以 func 作为前缀,用返回箭头 ->后跟返回类型的名称的方式来表示返回类型。

严格上来说,没有定义返回类型的函数会返回特殊的值,叫 Void。它其实是一个空的元组(tuple),没有任何元素,可以写成()

Swift 里输入参数可以做得很复杂,支持泛型、函数等,记住一条不变法则:不论嵌套几层,输入参数始终要有 () 包裹着。

被调用时,一个函数的返回值可以被忽略。

你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。

函数参数都有一个外部参数名(external parameter name)和一个本地参数名(local parameter name).外部参数名用来标记传递给函数调用的参数,本地参数名在实现函数的时候使用。(对于 Ruby 使用者而言,这是新的概念)

func someFunction(firstParameterName: Int, secondParameterName: Int) {
    // function body goes here
    // firstParameterName and secondParameterName refer to
    // the argument values for the first and second parameters
}
someFunction(1, secondParameterName: 2)

一般情况下,第一个参数省略其外部参数名,第二个以后的参数使用其本地参数名作为自己的外部参数名.所有参数需要有不同的本地参数名,但可以共享相同的外部参数名.

类似 Ruby 里传递哈希、或多参数,视觉上更好对应,自动匹配。只不过,对于 Swift 来说,感觉有点怪。

如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。(记得,以哈希的形式调用哈~)

func sayHello(to person: String, and anotherPerson: String) -> String {
    return "Hello \(person) and \(anotherPerson)!"
}

sayHello(to: "Bill", and: "Ted")
// prints "Hello Bill and Ted!"

如果你不想为第二个及后续的参数设置参数名,用一个下划线(_)代替一个明确地参数名。因为第一个参数默认忽略其外部参数名称,明确写下划线是多余的。(在这里,既然都忽略了,那干脆直接的、不使用此特性,不行吗)

你可以在函数体中为每个参数定义默认值(Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。
将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。

一个可变参数(variadic parameter)可以接受零个或多个值。函数调用时,你可以用可变参数来传入不确定数量的输入参数。通过在变量类型名后面加入(...)的方式来定义可变参数。
传入可变参数的值在函数体内当做这个类型的一个数组。

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。

但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。

通过在参数名前加关键字 var 来定义变量参数。

对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。

变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。

定义一个输入输出参数时,在参数定义前加 inout 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。

你只能将变量作为输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数前加&符,表示这个值可以被函数修改。(调用的时候,变量前加 & 表示传递的是引用)

输入输出参数是函数对函数体外产生影响的另一种方式。

每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。

使用函数类型

在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将函数赋值给它:

var mathFunction: (Int, Int) -> Int = addTwoInts

变量是 mathFunction 类型是函数 addTwoInts 而此函数的输入、输出类型分别是 (Int, Int)Int

函数类型作为参数类型

你可以用(Int, Int) -> Int这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现交由给函数的调用者。

下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果:

func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}

printMathResult(addTwoInts, 3, 5)
// prints "Result: 8"

被嵌套函数在调用时,函数参数为 in 前面的代码,函数体为 in 后面的代码。
被嵌套函数的类型是可预先知道的,并且定义调用同时进行。

函数类型作为返回类型

你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    return backwards ? stepBackward : stepForward
}

以第一个 -> 为分隔,输入参数类型为 (backwards: Bool),输出也就是返回类型为 (Int) -> Int

嵌套函数

全局函数,定义在全局域中。嵌套函数,定义在别的函数体中。

闭包

闭包采取如下三种形式之一:

  • 全局函数是一个有名字但不会捕获任何值的闭包
  • 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
  • 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包

sort(_:)方法需要传入两个参数:

  • 已知类型的数组
  • 闭包函数,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true,反之返回false

和 Ruby 里 map 等方法一样的,容易理解。

闭包表达式语法有如下一般形式:

{ (parameters) -> returnType in
    statements
}

parameters 表示输入参数,returnType 表示输出类型,in 是关键字表示开始执行内容,statements 表示执行内容。

根据上下文推断类型

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着您几乎不需要利用完整格式构造任何内联闭包。

也就是说,输入参数的类型和输出类型是可省的。

reversed = names.sort( { s1, s2 in return s1 > s2 } )

单表达式闭包隐式返回

单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式。

参数名称缩写

Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成。

运算符函数

reversed = names.sort(>)

省略到极致,真的能看懂吗?

尾随闭包

如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。
尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure({
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
  // 闭包主体部分
}

捕获值

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。
嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

闭包是引用类型

无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。

枚举

在 Swift 中,枚举类型是一等公民(first-class)。它们采用了很多传统上只被类(class)所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息,实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。

使用enum关键词来创建枚举并且把它们的整个定义放在一对大括号内:

enum SomeEnumeration {
  // enumeration definition goes here
}

里面的内容被称为 成员值

多个成员值可以出现在同一行上,用逗号隔开。

一旦一个变量,被赋值为一个枚举的成员值,你可以使用一个缩写语法(.)将其设置为另一个成员值

你可以使用switch语句匹配单个枚举值。

作为相关值的另一种选择,枚举成员可以被默认值赋值(语法上,和普通的赋值一样),其中这些原始值具有相同的类型(声明的时候要标注)。
你也可以在枚举类型开头加上indirect关键字来表示它的所有成员都是可递归的。

定义:

enum Result<T, U> {
  case Success(T)
  case Failure(U)
}

枚举,和类一样,可以当做普通变量的“类型”。

调用:

let aSuccess : Result<Int, String> = .Success(123)let aFailure : Result<Int, String> = .Failure("temperature too high")

注意:定义时对泛型的支持,调用时对泛型的指定。

类和结构体

类和结构体对比

Swift 中类和结构体有很多共同点。共同处在于:

  • 定义属性用于存储值
  • 定义方法用于提供功能
  • 定义附属脚本用于访问值
  • 定义构造器用于生成初始化值
  • 通过扩展以增加默认实现的功能
  • 实现协议以提供某种标准功能

与结构体相比,类还有如下的附加功能:

  • 继承允许一个类继承另一个类的特征
  • 类型转换允许在运行时检查和解释一个类实例的类型
  • 解构器允许一个类实例释放任何其所被分配的资源
  • 引用计数允许对一个类的多次引用

定义

类和结构体有着类似的定义方式。我们通过关键字classstruct来分别表示类和结构体,并在一对大括号中定义它们的具体内容。

生成结构体和类实例的语法一样。

通过使用 .,你可以访问实例中所含有的属性。
你也可以使用点语法为属性变量赋值。

所有结构体都有一个自动生成的成员逐一构造器,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:

let vga = Resolution(width:640, height: 480)

以哈希的形式传参,创建对应的实例,很正常啊。

结构体和枚举是值类型

在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。

类是引用类型

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

如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:

恒等运算符

  • 等价于 ( === )
  • 不等价于 ( !== )

指针

概念被弱化了,甚至可以说没有了。

绝大部分的自定义数据构造都应该是类,而非结构体。

Swift 中字符串(String),数组(Array)字典(Dictionary)类型均以结构体的形式实现。

属性

存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。

如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使定义了变量存储属性。

延迟存储属性

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

如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。

计算属性

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

如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称newValue

只读计算属性的声明可以去掉get关键字和花括号。

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。

可以为属性添加如下的一个或全部观察器:

  • willSet在新的值被设置之前调用
  • didSet在新的值被设置之后立即调用

计算属性和属性观察器所描述的模式也可以用于全局变量局部变量。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。

类型属性

为类型本身定义属性,不管类型有多少个实例,这些属性都只有唯一一份。这种属性就是类型属性

使用关键字static来定义类型属性。

跟实例的属性一样,类型属性的访问也是通过点运算符来进行。但是,类型属性是通过类型本身来获取和设置,而不是通过实例。

类似“类变量”。

方法

实例方法是属于某个特定类、结构体或者枚举类型实例的方法。

实例方法的语法与函数完全一致。

和调用属性一样,用点语法(dot syntax)调用实例方法。

方法的局部参数名称和外部参数名称 - 和函数一样。

修改方法的外部参数名称。

1)你可以自己添加一个显式的外部名称或者用一个井号(#)作为第一个参数的前缀来把这个局部名称当作外部名称使用。

2)相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(_)作为该参数的显式外部名称,这样做将覆盖默认行为。

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

使用self属性来区分参数名称和属性名称。
实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。

在实例方法中修改值类型

结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。

但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法。

要使用变异方法, 将关键字mutating 放到方法的func关键字之前就可以了。

在变异方法中给self赋值

变异方法能够赋给隐含属性self一个全新的实例。

枚举的变异方法可以把self设置为相同的枚举类型中不同的成员。

** 类型方法**

声明结构体和枚举的类型方法,在方法的func关键字之前加上关键字static

俗称的“类方法”。

下标脚本

下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。

与定义实例方法类似,定义下标脚本使用subscript关键字,显式声明入参(一个或多个)和返回类型。与实例方法不同的是下标脚本可以设定为读写或只读。这种方式又有点像计算型属性的getter和setter:

subscript(index: Int) -> Int {
    get {
      // 返回与入参匹配的Int类型的值
    }

    set(newValue) {
      // 执行赋值操作
    }
}

newValue的类型必须和下标脚本定义的返回类型相同。与计算型属性相同的是set的入参声明newValue就算不写,在set代码块中依然可以使用默认的newValue这个变量来访问新赋的值。

下标脚本允许任意数量的入参索引,并且每个入参类型也没有限制。下标脚本的返回值也可以是任何类型。下标脚本可以使用变量参数和可变参数,但使用写入读出(in-out)参数或给参数设置默认值都是不允许的。

一个类或结构体可以根据自身需要提供多个下标脚本实现,在定义下标脚本时通过入参个类型进行区分,使用下标脚本时会自动匹配合适的下标脚本实现运行,这就是下标脚本的重载

继承

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

如果要重写某个特性,你需要在重写定义的前面加上override关键字。

在合适的地方,你可以通过使用super前缀来访问超类版本的方法,属性或下标脚本。

你可以通过把方法,属性或下标脚本标记为final来防止它们被重写,只需要在声明关键字前加上final特性即可。

构造过程

构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。

你可以在定义构造器时提供构造参数,为其提供自定义构造所需值的类型和名字。构造器参数的功能和语法跟函数和方法参数相同。

Swift 会默认为每个构造器的参数自动生成一个跟内部名字相同的外部名,就相当于在每个构造参数之前加了一个哈希符号。(这是特性之一,也是和普通方法的区别之一)

如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显示描述它的外部名,以此重写上面所说的默认行为。

指定构造器和便利构造器

类的指定构造器的写法跟值类型简单构造器一样:

init(parameters) {
    statements
}

便利构造器也采用相同样式的写法,但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开。

convenience init(parameters) {
    statements
}

如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的。
你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)

带原始值的枚举类型会自带一个可失败构造器init?(rawValue:),该可失败构造器有一个名为rawValue的默认参数,其类型和枚举类型的原始值类型一致,如果该参数的值能够和枚举类型成员所带的原始值匹配,则该构造器构造一个带此原始值的枚举成员,否则构造失败。

通常来说我们通过在init关键字后添加问号的方式来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!),该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。

你可以在 init?构造器中代理调用 init!构造器,反之亦然。
你也可以用 init?重写 init!,反之亦然。
你还可以用 init代理调用init!,但这会触发一个断言:是否 init! 构造器会触发构造失败?

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

class SomeClass {
    required init() {
        // 在这里添加该必要构造器的实现代码
    }
}

当子类重写基类的必要构造器时,必须在子类的构造器前同样添加required修饰符以确保当其它类继承该子类时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加override修饰符:

class SomeSubclass: SomeClass {
    required init() {
        // 在这里添加子类必要构造器的实现代码
    }
}

如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
注意闭包结尾的大括号后面接了一对空的小括号。

析构过程

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

在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数。

解决实例之间的循环强引用

Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。

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

和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。你可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。

捕获列表中的每一项都由一对元素组成,一个元素是weakunowned关键字,另一个元素是类实例的引用(如self)或初始化过的变量(如delegate = self.delegate!)。这些项在方括号中用逗号分开。

如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方。

可空链式调用

在链式调用过程中 ?! 的使用。

错误处理

在Swift中,错误用符合ErrorType协议的值表示。

Swift枚举特别适合把一系列相关的错误组合在一起,同时可以把一些相关的值和错误关联在一起。因此编译器会为实现ErrorType协议的Swift枚举类型自动实现相应合成。

通过在函数或方法声明的参数后面加上throws关键字,表明这个函数或方法可以抛出错误。如果指定一个返回值,可以把throws关键字放在返回箭头(->)的前面。

在抛出函数体的任意一个地方,可以通过throw语句抛出错误。

当调用一个抛出函数的时候,在调用前面加上try。这个关键字表明函数可以抛出错误,而且在try后面代码将不会执行。

使用do-catch语句来就捕获和处理错误。

do {
    try function that throws
    statements
} catch pattern {
    statements
}

使用了 do 的其它语法:

do {
  let a = Animals.Troll
  ...
}

在运行时,有几种情况抛出函数事实上是不会抛出错误的。在这几种情况下,你可以用forced-try表达式来调用抛出函数或方法,即使用try!来代替try

使用defer语句来在执行一系列的语句。这样不管有没有错误发生,都可以执行一些必要的收尾操作。

类型转换

类型转换在 Swift 中使用 isas 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。

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

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

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

转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。

Swift为不确定类型提供了两种特殊类型别名:

  • AnyObject可以代表任何class类型的实例。
  • Any可以表示任何类型,包括方法类型(function types)。

嵌套类型

在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性名。

扩展

扩展就是向一个已有的类、结构体、枚举类型或者协议类型添加新功能(functionality)。

Swift 中的扩展可以:

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

声明一个扩展使用关键字extension

1)扩展可以向已有类型添加计算型实例属性和计算型类型属性。
2)扩展可以向已有类型添加新的构造器。
3)扩展可以向已有类型添加新的实例方法和类型方法。
4)通过扩展添加的实例方法也可以修改该实例本身。
5)扩展可以向一个已有类型添加新下标。
6)扩展可以向已有的类、结构体和枚举添加新的嵌套类型。

协议

协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为遵循(conform)这个协议。

协议的定义方式与类,结构体,枚举的定义非常相似。

要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。

如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。

1、对属性的规定

协议可以规定其遵循者提供特定名称和类型的实例属性(instance property)类属性(type property),而不指定是存储型属性(stored property)还是计算型属性(calculate property)。此外还必须指明是只读的还是可读可写的。

** 2、对方法的规定**

协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。

3、对Mutating方法的规定

有时需要在方法中改变它的实例。例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。

和 Ruby 里某些方法带 ! 是一个道理,因为调用这些方法后,会改变对象本身。或者说,具有破坏性。

4、对构造器的规定

协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体。

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

协议可以像其他普通类型一样使用,使用场景:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔。

类专属协议

你可以在协议的继承列表中,通过添加 class 关键字, 限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。

协议合成

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

protocol<SomeProtocol, AnotherProtocol> 前面的 protocol 是关键字,<> 里面是各个协议的名字。
协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。

检验协议的一致性

  • is操作符用来检查实例是否遵循了某个协议
  • as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
  • as用以强制向下转型,如果强转失败,会引起运行时错误。

协议可以含有可选成员,其遵循者可以选择是否实现这些成员。在协议中使用optional关键字作为前缀来定义可选成员。

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用where关键字来描述限制情况。

泛型

函数功能都是相同的,唯一不同之处就在于传入的变量类型不同。

泛型函数名后面跟着的占位类型名字(T)是用尖括号括起来的(<T>)。
你可支持多个类型参数,命名在尖括号中,用逗号分开。

当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

泛型函数使用了占位类型(通常此情况下用字母T来表示)来代替实际类型(如IntStringDouble)。

另外一个不同之处在于这个泛型函数名后面跟着的占位类型(T)是用尖括号括起来的(<T>)。这个尖括号告诉 Swift 那个T是函数所定义的一个类型。但因为T是一个占位类型,Swift 不会去查找命名为T的实际类型。

类型约束

你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。

关联类型

当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名)。作用于关联类型上实际类型在协议被实现前是不需要指定的。关联类型被指定为typealias关键字。

typealias ItemType。这个协议不会定义ItemType是什么的别名,这个信息将由任何遵循协议的类型来提供。

这里的 typealias 和类型别名,意义是不一样的。遵行此协议的“类”,总得有对应的“类型”吧。但是这个“类型”具体是什么,又不能确定,那么先用 ItemType 代替。(因为协议里面要用了,不能不提供;仅/只能提供一次)

Where 语句

对关联类型定义约束是非常有用的。你可以在参数列表中通过where语句定义参数的约束。一个where语句能够使一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

访问控制

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

模块指的是以独立单元构建和发布的FrameworkApplication。在Swift 中的一个模块可以使用import关键字引入另外一个模块。

源文件指的是 Swift 中的Swift File,就是编写 Swift 源代码的文件,它通常属于一个模块。尽管一般我们将不同的 分别定义在不同的源文件中,但是同一个源文件可以包含多个函数 的定义。

Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。

  • public:可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。通常情况下,Framework 中的某个接口是可以被任何人使用时,你可以将其设置为public级别。
  • internal:可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。通常情况下,某个接口或Framework作为内部结构使用时,你可以将其设置为internal级别。
  • private:只能在当前源文件中使用的实体,称为私有实体。使用private级别,可以用作隐藏某些功能的实现细节。

public为最高级访问级别,private为最低级访问级别。

如果你不为代码中的所有实体定义显式访问级别,那么它们默认为internal级别。

单元测试目标的访问级别

默认情况下只有public级别的实体才可以被其他模块访问。然而,如果在引入一个生产模块时使用@testable注解,然后用带测试的方式编译这个生产模块,单元测试目标就可以访问所有internal级别的实体。

高级运算符

Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(&+)。所有的这些溢出运算符都是以 & 开头的。

  • 溢出加法 &+
  • 溢出减法 &-
  • 溢出乘法 &*

位运算符

1)按位取反运算符(~)
2)按位与运算符(&)
3)按位或运算符(|)
4)按位异或运算符(^)
5)按位左移运算符(<<)和按位右移运算符(>>)

类和结构可以为现有的操作符提供自定义的实现,这通常被称为运算符重载(overloading)。

类与结构体也能提供标准单目运算符(unary operators)的实现。单目运算符只有一个操作目标。当运算符出现在操作目标之前时,它就是前缀(prefix)的(比如 -a),而当它出现在操作目标之后时,它就是后缀(postfix)的(比如 i++)。

要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 func 关键字之前指定 prefix 或者 postfix 限定符。

复合赋值运算符(Compound assignment operators)将赋值运算符(=)与其它运算符进行结合。比如,将加法与赋值结合成加法赋值运算符(+=)。

还可以将赋值与 prefixpostfix 限定符结合起来。

不能对默认的赋值运算符(=)进行重载。只有组合赋值符可以被重载。同样地,也无法对三目条件运算符 a ? b : c 进行重载。

自定义的类和结构体没有对等价操作符(equivalence operators)进行默认实现,等价操作符通常被称为“相等”操作符(==)与“不等”操作符(!=)。

新的运算符要在全局作用域内,使用 operator 关键字进行声明,同时还要指定 prefixinfix 或者 postfix 限定符。

自定义中缀运算符的优先级和结合性

结合性(associativity)可取的值有leftrightnone。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的操作数进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的操作数进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。

结合性(associativity)的默认值是 none,优先级(precedence)如果没有指定,则默认为 100

推荐阅读更多精彩内容