Swift 语法初窥
References: The Swift Programming Language
某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:
hello world
。
1 | print("Hello, World") |
Swift 的基础语法
值与类型
定义变量用 var
,常量用 let
。常量只能赋值一次,但不用必须在声明的时候去赋值。
1 | var myVariable = 42 |
编译器会自动推断常量和变量的类型,但是如果推断不出来(比如说没有初始值等),就需要声明类型。
1 | let helloTalk : String |
Swift的值不会隐式被转为其他类型
1 | let widthFloat = 93.33 // 自动推断 为 Double |
创建、定义一个数组或者字典
1 | let emptyArray = [String]() |
数组和字典都是集合类型,对于这种类型的 let
和 var
修饰并非是像普通值的可否赋值。
比如说,用 let
修饰的数组是不能添加和移除数组中的元素,数组中的元素个数、位置均不可变,但是用 var
修饰的数组可以添加/删除元素。
枚举
枚举是为一组相关的值定义了一个共同类型,在 Swift
中,枚举是“一等公民”。枚举成员的原始值不仅可以是整形,还可以是字符、字符串、浮点型;此外,枚举还支持属性、方法、甚至是构造函数、扩展和遵守协议。
用 enum
关键词来创建枚举:
1 | enum Direction { |
原始值
1 | enum Direction: String { |
关联值
Swift 的枚举可以存储各个类型的关联值,而且每个成员的类型可以都不一样。所以对于一个网络请求可以有这样的抽象:一个网络请求的结果,可以是成功或者失败的,如果成功则返回的是我们想要的数据,不成功返回错误原因,那么可以写成这样:
1 | enum Result<Value> { |
Value
是泛型语法,可以是任何你需要的类型。
控制流
for-in
1 | let persons = ["person1","person2"] |
if-else
1 | if 2>1 { |
if
后面必须是布尔表达式,如果是一个值的话不会隐式的与 0 比较。
switch
1 | let vegetable = "red pepper" |
switch
不仅支持基本数据类型。另外Swift中的switch
语法可以省略break
。但是不能省略 default
,会报 Switch must be exhaustive, consider adding a default clause
的编译错误。
while
1 | var i = 1 |
Swift 的函数和闭包
函数的关键字是 func
,函数定义的格式是:
1 | func funcName(para:paraType) -> returnType{ |
函数的参数标签
其中参数的那部分的详细结构是用小括号括起来,参数名,冒号,参数类型: (number:Int)
。
在默认情况下,函数的参数标签使用参数名,或者用 _
不使用参数标签,也可以自定义标签,我们定义一个奇葩的函数:
1 | func 求和(数字1 num1:Float, 数字2 num2:Float) -> Float { |
求和 就是方法名,数字1 就是自定义的参数标签。调用时会显示标签:
1 | let sum = 求和(数字1: 2, 数字2: 4) |
函数返回多个值
swift
还可以用元组返回多个返回值:
1 | func compare(numarray:([Int])) -> (min:Int, max:Int) { |
调用时获取返回值:
1 | compare(numarray: [1, 2, 3, 4, 5]).max |
函数嵌套函数
swift
语法中函数可以嵌套函数,用于分割太长或者太复杂的函数:
1 | // 不要在意逻辑,只是为了示例一下。。。 |
返回一个函数
函数还可以用一个函数做为返回值
1 | func makeMethod() -> ((Int)->(Int)) { |
函数调用:
1 | print("makeMethod()(1993): ",makeMethod()(1993)) |
传入一个函数
函数可以把一个函数当做返回值返回,也可以当做一个参数来传入:
1 | func sumOfMaxMin(numarray:([Int]),compare:(_ numarray:([Int]))->(min:Int, max:Int)) -> (Int) { |
可以看到, sumOfMaxMin
函数有两个参数:numarray:([Int])
和 compare:(_ numarray:([Int]))->(min:Int, max:Int)
。其中 compare
是一个函数。
在调用的时候:
1 | var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5],compare: compare) |
compare
是上个例子中的函数。当然,我们也可以不传入一个现成已经定义和实现的函数:
1 | var sumOfMaxMinValue = sumOfMaxMin(numarray: [1, 2, 3, 4, 5]) { (numarray:([Int])) -> (min: Int, max: Int) in |
函数是一种特殊的闭包
大家伙看到这里,肯定会一拍大腿:哎呦,这玩意不就是闭包嘛!
(The Swift Programming Language)函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变量和函数,即使闭包是在一个不同的作用域被执行的
我们可以使用{}来创建一个匿名闭包。使用in将参数和返回值类型声明与闭包函数体进行分离。
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
闭包的简写
如果闭包的类型已知,那么可以省略参数和返回值的类型
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
单个语句闭包会把它语句的值当做结果返回
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
如果把上面的闭包写成一行的话
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
我们可以通过参数位置而不是参数名字来引用参数,那么上面的代码就变成这样
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
当一个闭包是传给函数的唯一参数,我们可以完全忽略括号
1 | let numArray:([Int]) = [1, 2, 3, 4, 5] |
Swift 的类和对象
类的定义
类定义的关键字是 class
,我们用 class
+ 类名 + “:“ + 父类,比如定义一个 Person
类,类中去声明和定义变量和函数:
1 | class Person: NSObject { |
类的扩展
1 | extension Person { // 给人加一个飞的功能... |
属性
存储属性
存储属性就是存储在类或者结构体一个实例里的一个常量和变量,用 var
或者 let
修饰。
但是如果一个结构体实例被声明为常量,那么即便这个结构体的某个属性是变量,也是不能去改变的:
1 | let point = CGPoint(x: 1, y: 1) |
延迟存储属性
1 | lazy var nameLabel:UILabel = { |
其实就是把一个立即执行的闭包的返回值赋值给属性,以达到懒加载的目的。
属性观察器
willSet
在新的值被设置之前调用didSet
在新的值被设置之后立即调用
需要注意的是:当为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者。
计算属性
计算属性提供了一个 getter
和一个可选的 setter
。属性的 getter
和 setter
的关键词是 get
和 set
,在 setter
中新值是 newValue
1 | class Person: NSObject { |
只读计算属性
当一个属性只有 getter
而没有 setter
时,那么它就是一个只读计算属性
类对象的实例和访问:
1 | var person = Person() |
类的构造和析构
我们可以给 Person
自定义一个构造函数,构造函数中需要给所有的存储型属性一个赋值或者默认值:
1 | init(name: String) { |
这样我们就可以用 name
来实例化一个对象: var person = Person(name:"Tom")
如果所有属性都有默认值,我们没有自定义的构造,系统会生成一个默认的构造函数:var person = Person()
相应的,如果我们自定义了一个构造函数,那么系统便不会为该类生成默认的构造函数。
但是,我们可以把自定义的构造函数写到类的扩展(extension
)里,而不是类的原始定义里面。
与构造函数对应的是析构函数:deinit
指定构造器和便利构造器
指定构造器里面会初始化类实例所有的属性,所以为了保证继承的属性也能被初始化,子类的指定构造器都会调用父类的指定构造器,而类的便利构造器总是会调用本类的指定构造器。
也就是说:指定构造器需要向上调用,便利构造器需要横向调用
所以,我们可以把父类的指定构造函数重写成便利构造,却不能重写父类的便利构造。即便重写了父类的便利构造函数,但是由于我们不能直接调用父类的便利构造函数,所以不用给函数以 override
修饰。
必要构造器 如果某个构造函数被 required
修饰,那么该函数就是必要构造函数,子类继承该类时都必须要实现改构造函数。在子类重写父类的构造函数的时候,也要加 required
来修饰,以确定继承链上子类的子类也遵守。
除了构造和析构函数,类还有实例的私有函数、公共函数和静态函数。
类、函数的访问控制(访问、继承/重载 权限)
Swift
中的访问控制有 模块 和 源文件 两个概念。用 “import” 导入的就是模块。
对于类而言的修饰词与权限:
修饰词 | 权限 |
---|---|
open |
修饰的类可以随意继承与访问 |
public |
修饰的类只能在本模块内被继承,但是可以随便访问 |
internal |
默认 - 模块内拥有访问权限 |
fileprivate |
是文件外部不能被访问 |
private |
是文件内部不能被访问 |
final |
是文件内部也不能被继承 |
当然了,对于一个 internal
的类,其属性和方法的级别是不会超出类本身的,比如不可能是 public
。
函数也是同样的修饰词和权限,只是类的继承对应函数的重载权限。
函数除了以上几个,常用的修饰词还有 static
和 override
。
static
修饰的是类方法。override
修饰的是重写父类的方法。
Swift 中的类和结构体
Swift
中的结构体的能力被大大加强,不仅可以拥有属性,还以有方法、构造函数、甚至是扩展和遵守协议。这样的结构体和类有很多相同点:
- 属性:存储数据
- 方法:提供一些功能
- 下标:可以使用下标语法
- 构造器:生成初始化值
- 扩展:增加功能
- 协议:提供某种通用功能
当然,类和结构体也有很多不同的地方,类还有许多独有的附加功能:
- 继承:一个类可以继承另一个类的特征
- 类型转换:运行时检查和解释一个类实例的类型
- 析构器:一个类实例释放任何其所被分配的资源
- 引用计数:对一个类的多次引用
结构体会提供一个默认的构造函数,这个构造函数是结构体所有的属性分别作为参数来构建:
1 | struct MyPoint { |
结构体和枚举都是值类型,值类型在赋值(给变量或者常量)和传递(作为参数给一个函数)的时候都会被拷贝,值类型实例的值属性也会被拷贝。Swift
中的整型、浮点型、布尔型、字符串、字典、数组都是值类型,底层都是由结构体来实现的。
类是引用类型,引用类型在赋值和传递的时候,内容并不会被拷贝。因此赋值的实例和被赋值的实例其实是一份内容,内容在内存中也是一份。
值类型和引用类型的区别在于,值类型在赋值和传递的时候是深拷贝,而引用类型是浅拷贝。
深拷贝就是把内存地址中存放的内容也拷贝一份内存中的内容就会有两份;而浅拷贝只是拷贝了内存的地址,内存中的内容还是只有一份。
但需要注意的是,在 Swift
中,并不是值类型一旦被赋值和传递的时候就会被拷贝一份,只有当需要的时候,比如被赋值的实例去改变内容的时候才会真正的去拷贝。
那么,我们到底如何选择结构体或者类呢?如果你只是用来做以下功能是可以选择结构体:
- 只是用来封装一些相关的数据
- 这些数据被赋值或者传递的时候会被拷贝一份
- 不需要被继承
比如 CGPoint
、CGRect
、CGSize
等都是结构体。