之前学习了 swift 的基础语法,写过一些小 demo。但项目中一直没有使用,不过还是要时不时回顾下,以后还是会用到。下面是我复习 swift3.0 基础语法做的一些摘抄和笔记。
语言特性
swift 是一门类型安全的语言。
swift相比于Objective-C又一个重要的优点,它对函数式编程提供了很好的支持,Swift提供了map、filter、reduce这三个高阶函数作为对容器的支持。
基础部分
隐式解析可选
隐式解析可选类型,在第一次被赋值之后,变量一定有值,这个时候采用隐式解析可选类型,不用每次访问都去判断和解析。(例如:string!)
注意:如果直接访问一个没有值的隐式解析可选类型会发生运行时错误,和强制解包一个没有值的普通可选类型一样。
空合运算符
a ?? b 其实就是 a != nil ? a! : b
区间运算符
闭区间(a...b):定义一个包含从a
到 b
的闭区间(包括a
和b
)
半开半闭区间(a..<b): 定义一个包含从a
到 b
包括a
但不包括 b
的区间
集合
集合 Set :当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
Swift 的所有基本类型(比如 String , Int , Double 和 Bool )默认都是可哈希化的,可以作为集合的值的类型或 者字典的键的类型。没有关联值的枚举成员值默认也是可哈希化的。
switch 贯穿
swift 中 switch 不需要显示插入 break
,它默认不会自动从上一个 case 落入下一个 case,即当一个 case 匹配并完成它的执行语句,整个 switch 代码块就完成了。
如果需要像 C 语言一样贯穿下来,就得使用 fallthrough 关键字。
条件判断
swift 中使用 if ,guard 判断条件执行。
guard 语句判断条件为真,继续执行后面的程序,否则执行 else 分支里面的程序。使用 guard 相比于 if 可以提升代码可读性。
函数修改参数值
需要在参数定义前加 inout
关键字,同时需要在输入的参数面前加 &
符号。
注意:传入的参数必须是变量,是常量或者字面量是不可被修改的。
函数类型
swift 中函数可以像其他类型一样,可以被定义为一个变量或者常量,并用于另一个函数的参数返回值。
swift 中你还可以把一个函数定义在另一个函数内部,这种叫做嵌套函数
闭包
闭包是自包含(自包含:组件不依赖其他组件,能够以独立的方式供外部使用)的函数代码块,swift 中的闭包和 OC 中的代码块(block)比较类似。
闭包的表达式语法形式如下:
{ (parameters) -> returnType in
statements
}
闭包的函数体部分由关键字 in 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
该闭包 {(s1: String, s2: String) -> Bool in return s1 > s2}
经过上下文推断,隐式返回,参数名称缩写,最后可以表示为 {$0 > $1}
尾随闭包
当闭包作为函数最后一个参数的时候,可以使用尾随闭包的来增强代码可读性。例如:reversedNames = names.sorted() { $0 > $1 }
当闭包为唯一参数的时候可以把 () 省略,写成 reversedNames = names.sorted { $0 > $1 }
值捕获
闭包可以在其被定义的上下文中捕获变量或常量,既使定义这些变量或者常量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
例如:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
incrementer()
捕获了外部变量 amount
和 runningTotal
的引用。并在内部修改了 runningTotal
的值。
注意:为了优化,当一个值被捕获之后,不会被闭包改变或者在闭包创建之后不会改变,swift 可能会将捕获引用修改为拷贝一份相同的值,同时会负责相关的内存管理工作。
类型:闭包和 Class 是引用类型,Struct 和 Enum 是值类型。所以当闭包为某个类实例的属性,而闭包又直接或者间接的引用了该类的实例,就会造成循环引用。
逃逸闭包
当一个闭包作为一个参数被传入到函数中,但是这个闭包在函数返回之后才执行,我们就称该闭包从函数中逃逸。在参数前添加 @escaping
关键字标识该闭包允许逃逸。
自动闭包
自动闭包是一种自动创建的闭包,用于包装传递给函数的参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会直接返回被包装在其中的表达式的值。种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
例如:
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
, 调用的时候只需要 customerProvider()
。
可以在参数面前加 @autoclosures
标识这是一个自动闭包。
例如:
// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印 "Now serving Ewa!"
若没有用 @autoclosures
标识为自动闭包,则需要加花括号调用 serve(customer: {customersInLine.remove(at: 0)})
当然我们可以将逃逸闭包和自动闭包结合起来使用,在参数前面同时添加 @autoclosure
和 @escaping
标识。
类和结构体
结构体
结构体有一个自动生成的逐一成员构造器,用于初始化所有的成员的属性。
结构体通多点语法访问实例的属性。
结构体和枚举是值类型
类
类是引用类型,通过恒等运算符(=== 和 !==)判断两个变量或者常量是否是同一引用。
一 个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地 址,也不要求你使用星号( * )来表明你在创建一个引用。
类和结构体的选择
按照通用的准则,当符合一条或多条以下条件时,建议构建结构体:
- 该数据结构的主要目的是用来封装少量相关简单数据值。
- 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
- 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
- 该数据结构不需要去继承另一个既有类型的属性或者行为。
举例来说,以下情境中适合使用结构体:
- 几何形状的大小,封装一个 width 属性和 height 属性,两者均为 Double 类型。
- 一定范围内的路径,封装一个 start 属性和 length 属性,两者均为 Int 类型。
- 三维坐标系内一点,封装 x , y 和 z 属性,三者均为 Double 类型。
Swift 中许多基础类型,例如:String,Array 和 Dictionary 均以结构体的形式实现。
注意:String,Array 和 Dictionary 的赋值看似总会发生拷贝,但是 swift 会在绝对必要时才真正执行拷贝,以此来保证内存最优。
属性
存储属性
一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。
当一个结构体被赋值给一个常量之后,就无法修改该实例的任何属性,即使是被申明为变量的属性。这是由于当结构体为值类型,当其常量时,内部的所有属性也就成了常量。
类是属于引用类型,讲一个类赋值给一个常量之后,仍然可以修改其内部的变量属性。
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性前面使用 lazy
关键字来标识这是个延迟存储属性。
全局的常量或变量都是延迟计算的,不同的地方在于,全局的常量或变量不需要标记 lazy
修饰符。
注意:
必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储。Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法 直接访问。
计算属性
除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
只读计算属性:只有 getter 没有 setter 的计算属性就是只读计算属性。
必须使用 var
关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let
关键字只用来声明常量属性,表示初始化后再也无法修改的值。
属性观察器
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
可以为属性添加如下的一个或全部观察器:
willSet
在新的值被设置之前调用didSet
在新的值被设置之后立即调用
注意:
父类的属性在子类的构造器中被赋值时,它在父类中的观察器会被调用,随后才会调用子类的观察器。
如果将属性通过 in-out 方式传入函数,观察器也会被调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。
类型属性
类型本身定义的属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
使用关键字 static 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 class 来支持子类对父 类的实现进行重写。
注意:
跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过 程中使用构造器给类型属性赋值。
存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访 问,系统也保证只会对其进行一次初始化,并且不需要对其使用 lazy 修饰符。
方法
实例方法
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。
结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。
如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择可变(mutating) 行为,然后就可以从其方法内部改变它的属性;
例如:
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)"
类型方法
在方法的 func 关键字之前加上关键字 static ,来指定类型方法。类还可以用关键字 class 来允许子类重写 父类的方法实现。(和类型属性一样)
重写
重写方法
你需要在重写定义的前面加上 override
关键字
重写属性
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
可以给方法,属性或下标添加 final
来防止被重写
构造过程
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
注意:
- 当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
- 对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。(当然)
默认构造器
如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默 认构造器(default initializers)。这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
结构体的逐一成员构造器
除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。
假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器 写到扩展( extension )中,而不是写在值类型的原始定义中。
类的继承和构造过程
类构造器的代理规则
- 指定构造器必须调用其直接父类的指定构造器
- 便利构造器必须调用同类中的其他构造器
- 便利构造器必须最终导致一个指定构造器
类的两段式构造
阶段 1
- 某个指定构造器或便利构造器被调用。
- 完成新实例内存的分配,但此时内存还没有被初始化。
- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
- 指定构造器将调用父类的构造器,完成父类属性的初始化。
- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
- 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完 全初始化。此时阶段 1 完成。
阶段 2
- 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问 self 修改它的属性并调用实例方法等等。
- 最终,任意构造器链中的便利构造器可以有机会定制实例和使用 self 。
析构
析构器会在实例释放之前被调用,并且无法手动调用。可以在这里处理一些关闭文件的操作。
deinit {
// 执行析构过程
}
解决循环引用
打破循环引用(类之间才会有),使用弱引用(weak)和无主引用(unowned),若引用和 OC 基本差不多,无主引用用于其他实例有相同或者更长的声明周期,例如人和银行卡。
重要:使用无主引用,你必须确保引用始终指向一个未销毁的实例; 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。
扩展
在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能。
扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
扩展可以添加新的计算属性,但是不可以添加新的存储属性,也不可以为已有属性添加属性观察器。
扩展可以为类型添加新的构造器和方法(实例方法和类型方法)
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 self 或其属性的方法必须将该实例方法标注为 mutating
协议
你可以在协议的继承列表中,通过添加 class 关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 where 子句来描述。
泛型
请始终使用大写字母开头的驼峰命名法来为类型参数命名,以表明它们是占 位类型,而不是一个值。
Any
和泛型的区别,Any
会避开类型检测,泛型不会。
泛型也可以使用 where
语句来限定类型
访问级别
函数类型
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
开放访问和公开访问可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件 里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为开放或者公开访 问。
open
:
- open 修饰的 class 在 Module 内部和外部都可以被访问和继承
- open 修饰的 func 在 Module 内部和外部都可以被访问和重载(override)
public
:
- public 修饰的 class 在 Module 内部可以访问和继承,在外部只能访问
- public 修饰的 func 在 Module 内部可以被访问和重载(override),在外部只能访问
internal
:内部访问可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况 下,某个接口只在应用程序或框架内部使用时,你可以将其设置为内部访问。
filepart
:文件私有访问限制实体只能被所定义的文件内部访问。当需要把这些细节被整个文件使用的时候,使用文件 私有访问隐藏了一些特定功能的实现细节。
private
:私有访问限制实体只能在所定义的作用域内使用。需要把这些细节被整个作用域使用的时候,使用文件私有 访问隐藏了一些特定功能的实现细节。