[D.2]对Swift语法整理的非常好,这里主要基于这个文档提取出Swift相对于C/C++,Objective-C而言相对突出的语法。
不需要(加上也没错)在语句结尾加分号了(这个习惯可能改起来比较麻烦);与此同时,两个语句最好要写两行了~,非要写在一行的话,就要添加分号来分割了。
原生类型(值类型)
Bool
Int Int8 Int16
UInt UInt8 UInt16
Float
Double
nil
Void
可选类型
如果可选类型有值,它不等于nil;否则就是nil。
var Name: String? = "some name value"
如果知道可选值是有值的,可以强制解析:
if Name != nil {
var anotherName = Name!
}
可选绑定
if let constantName = someOptional {
statements
}
隐式解析可选类型
在可选类型值的后面加一个!符号,表示确定该值一定不为nil,可以当作非可选类型用。这个时候就说该值是隐式解析可选类型。
但是如果该值为nil的话,会导致运行时错误。所以要小心确保有值。
数值类型字面量
二进制 0b开头
八进制 0o开头
十六进制 0x开头
数值较大,数字较多的时候,可以使用下划线分割:982_803_020_12.022_329
类型别称
typealias NewIntType = UInt16
基本运算符
兼容C的
赋值运算符没有返回值
空合运算符
a??b
解释:a != nil ? a! : b
区间运算符
a...b
解释:[a, b]
a..<b
解释:[a, b)
Unicode标量
let blackHeart = "\u{2665}"
let sparklingHeart = "\u{1F496}"
集合
数组 Arrays
创建空数组 let emptyAry = [Int]()
数组赋空 emptyAry = []
支持+、+=操作符,连接两个数组
集合 Sets
集合的成员必须是支持哈希的,所有基本类型都是可哈希的
集合创建,访问,修改
集合的操作
集合的成员关系
字典 Dictionaries
字典的键值必须是支持哈希的
字典的创建,访问,修改
简单值
使用let来声明常量,使用var来声明变量。
使用方括号[]来创建数组和字典,并使用下标或者键(key)来访问元素。
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。
控制流
使用if和switch来进行条件操作,使用for-in、for、while和do-while来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。
switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
元组
可以作为函数的返回值,参数……
函数
一等公民,而且可以在函数定义内部嵌套函数定义,函数可以直接作为参数传递,当然这就使用到了函数原型。
函数的本质是一个命名的闭包。
也有一种匿名闭包,直接使用大括号包起来的可执行代码片段。
类和对象
构造器init
析构器deinit
类和结构体的区别是,类对象传引用,结构体对象传值。
枚举
可以自定义方法
协议
protocol
泛型的支持
错误处理
throws
try
断言
assert()
控制流
for-in
while condition {
}
repeat {
} while condition
if condition {
}
switch value {
case value 1:
response to value 1
case value 1:
response to value 2
default:
default response
}
没有break
如果需要多种情况合并,可以把标签用逗号分隔放在一个case中。
case value1, value2:
statements
区间匹配
case 1 ..< 5
statement
元组匹配
case (0, 0):
statements
case (_, 1):
statements
元组的值绑定
值绑定的条件分类 where
控制转移语句
continue
break
fallthrought
配合swift中的switch-case完成C语言中的自动执行;该语句放在case语句的最后一句;
return
throw
guard-else
不同于if-else,guard必须有一个else
API监测
swift内置支持API监测,防止代码在不合适的机器上执行
格式: if #available()
函数
元组作为返回值的时候,可以使用可选类型:(type1, type2, ...)?
函数参数包含:参数标签,参数名,参数类型
func FuncName(参数标签1 参数名1:参数类型1, ……){
}
不想要参数标签的时候,可以使用_表示省略。
调用方使用参数标签
函数内部使用参数名称+类型
参数标签是函数名称的一部分。
swift中默认参数是常量,如果要修改需要在参数名之前添加inout关键字
函数类型
由参数类型+返回类型决定
这一点不同于C/C++的函数
可以使用类型推断来声明函数类型的变量。
嵌套函数
定义于函数内部,可用于外围函数的返回类型,参数,以及调用。
闭包
下面是越来越省略的写法
典型闭包写法:
var names = ["ustone", "jhon", "mick"];
var reverseNames = names.sorted(by: {(s1:String, s2:String) -> Bool in
return s1 > s2
})
根据上下文推断:不需要写类型了,参数列表的括号也省略了
reverseNames = names.sorted(by: {s1, s2 in return s1 > s2})
单表达式闭包隐式返回:省略return
reverseNames = names.sorted(by: {s1, s2 in s1 > s2})
参数名称省略:
reverseNames = names.sorted(by: {$0 > $1})
运算符方法:
reverseNames = names.sorted(by: >)
print(reverseNames)
尾随闭包
用于:要将一个闭包作为函数的最后一个参数传入时
像这种事典型的闭包调用:
reverseNames = names.sorted(by: {$0 > $1})
尾随闭包的调用:
reverseNames = names.sorted(){$0 > $1}
如果调用函数只有一个参数,那么括号也可以省略:
reverseNames = names.sorted{$0 > $1}
闭包中的值捕获
值捕获可以保证所捕获的值在超出作用域之后不消失。
闭包本身是引用类型
即使为同一个闭包使用多个变量保存,并调用,最终始终是操作一个东西
逃逸闭包
在函数之外调用,一般用于先保存,后使用的情景;需要为这类闭包添加@escaping关键字
自动闭包
自动创建,见于无参数的闭包类型。自动闭包可用于延时计算。
枚举 (值类型)
一般情况下使用全路径的枚举;如果可以推断出来可以使用简洁的方式:.开头的形式
递归枚举
类和结构体
类属于引用类型,传参的时候计数自增
结构体属于值类型,传参的时候拷贝
结构体的初始化,可以使用按序初始化的方式
类实例是否引用了同一个实例,使用等价判断===,以及不等价判断!==;==用来判断实例的值是否一样
属性
存储属性:类或者结构体的实例中的一个常量或者变量
计算属性:setter & getter,如果只有getter,就是只读计算属性了,如果是只读的话,可以省略get,花括号这样的符号了
结构体实例如果赋于一个常量,那么其属性即使是var类型,也不可以被修改(由结构体是常量决定);类实例者不然。
延迟属性,在定义的时候,在属性声明前加上lazy关键字;用于初始化需要大量计算,但是又可能用不到的数据。具体初始化时间在第一次访问的时候。lazy不支持线程安全。
属性观察器:willset,didset;不过延迟属性不能添加属性观察器
类型属性:类似C语言的静态常量,属于多个类实例共享一个属性值的属性;使用static关键字,还可以使用class关键字,表示子类可以重写。
可选类型属性
方法
结构体和枚举类型是值类型,默认方法不可以修改属性;如果想要这样做的话,需要声明方法为mutating。还可以在可变方法中为self赋值(这在C++中是不可想象的)。
类型方法 在func前面加static;也可以在func前加class,表示子类可以重写;类型方法中的self表示类型本身,类型方法内部调用其他类型方法不需要加类型限定
下标
subscript
重写
必须加override,不然报错;可以通过super来访问超类的方法
计算属性也可以被重写;不过不能扩展只读属性为读写属性
属性观察器也可以重写
下标访问也可以重写
防止重写 final,可以作用于上面的所有重写类型
构造过程
目标是保证新实例在使用前被设置正确初始化
存储属性的初始化可以在定义的时候,也可以在构造器中
由于swift可以在定义属性的时候赋值,所以构造器功能相比C++的构造函数功能偏弱
可选属性类型:构造器中不用初始化它
常量被赋值的地方也就是在定义的时候,以及在构造器中
默认构造器:如果每一个属性都有默认值就会有一个;结构体中如果没有定义构造器的话,就会有一个逐一成员构造器(类似C中的初始化结构体方式)
构造器代理:值类型和类类型两种;前者不支持继承,后者支持继承。可以在init中调用self.init的其他形式
构造器分为:指定构造器和便利构造器,后者是在init前面加上convenience关键字。在继承结构中,前者向上调用,后者水平调用且必须最后调用到一个指定构造器。
子类重写与超类同型的构造器的时候,也需要加override关键字,即使是分属指定构造器和便利构造器两类也需要加override
默认情况下,子类不会继承父类的构造器;但是如果子类没有定义构造器,就会继承;如果子类重写了父类的所有指定构造器,那么还会自动继承父类的便利构造器
可失败构造器,在init后面加?,内部使用return nil表示构造失败;此类对象,在使用前需要判断是否为nil;子类可以重写父类的可失败构造器,也可以使用一个非可失败构造器来重写父类的可失败构造器。
另一种可失败构造器,init!(...),失败之后会引发一个断言
必要构造器:required init(...),子类重写的时候必须加required关键字
使用闭包设置属性默认值
析构函数
deinit,无参数,无返回值,一个类最多一个原型定义
强引用类型 & 弱引用类型和无主引用类型
相互包含的两个对象之间使用强引用的话,会产生无法释放的问题;
弱引用类型是在声明的时候加上weak关键字;弱引用对象被设置为nil的时候,属性观察器不会被触发;弱引用总是可选类型。
无主引用,关键字unowned,仅用于类,或者类作用域的协议类型。
防止出现强引用类型造成循环引用的情况有3种:
1. 二者皆不依赖对方存在
一强一弱引用的方式解决
2. 其中一个依赖另一方,反之则不然
一强一无主引用的方式解决
3. 彼此依赖对方的存在,共存亡
无主引用配合隐式解析可选类型属性(Type!)
闭包作为类的属性引起的循环引用,是由于闭包属于引用类型,闭包内使用类的属性,必须使用self来显式限定,这样也可以提醒你引用了类实例要引起注意
使用捕获列表:方括号内成对出现的参数,weak/unowned类型+类实例/初始化过的类实例属性
那么使用weak还是unowned?
闭包本身和捕获的实例/属性同时消亡的话,使用unowned来修饰被捕获对象
被捕获的引用实例/属性可能为nil的时候,使用weak来修饰被捕获对象
被捕获的引用实例/属性不可能为nil的时候,使用unowned来修饰被捕获对象
可选链式调用
可选链式调用返回的是可选类型,不管多少层
访问属性
访问下标
访问可选类型的下标
可选链式调用可以简化条件判断,这一点比之前的语言是一大特色
错误处理
try、try?、try!:try表示后面的函数可能抛出异常,try?会在抛出异常的时候将返回值转为可选类型,try!表示禁用错误传递,在你确定不会抛出异常的时候使用,不过一旦有异常抛出的话,就会有一个运行时断言错误。
throwing函数:带有throws关键字的函数,构造器可也以是throwing函数
do-catch控制流,相当于C++中的try-catch
可以配合defer语句做好资源释放
类型转化
is:用来做类型检查
as:用来做向下转化;条件形式as? 可选绑定,强制形式as!
Any:任意不确定类型,包括可选类型,函数类型,闭包;可用来定义数组,装载不一致类型;当你用一个Any类型表示一个可选值的时候,会有一个警告,这时候可以使用as Any来消除警告
AnyObject:任意不确定类类型实例
嵌套类型
这一点C++中也有,类内部可以定义类,并且可以多层嵌套。
扩展
可以对已有的类,结构体,枚举类型,协议类型添加新的计算型属性,计算型类型属性,(可变mutating)实例方法,类型方法,新的(便利)构造器,下标(subscript),嵌套类型,使一个已有的类型符合某协议
扩展只是添加,不能重写已有方法;并且不能添加存储类型属性
协议
protocol关键字代替class定义类类型时候的位置。
不指定属性是存储类型还是计算类型,只管属性的类型和名称
总是用var声明变量属性,使用static,class来修饰类型属性
定义实例方法和类方法的时候,不需要写方法体,当然是可以写的。
mutating方法:实现mutating方法的时候,如果是类类型,不需要加该关键字,结构体和枚举类型是必须要加的。mutating表示该方法可能会修改属性的值
构造器:
协议中可以定义构造器这一点比较新颖;实现类中的init前必须加required关键字。
可失败构造器:init?,init!
协议可以当作类型来使用
协议可以继承协议,并支持多继承
类类型专属协议:继承的时候在继承列表的第一位上写class,如果非类类型实现该协议,会有编译时错误
协议合成:使用&连接两个或多个协议类型,用来约束表示某实例(比如说做形参的时候)必须实现了这几个协议
检查协议一致性:is,as
可选协议:一般在需要和objective-c交叉的时候使用,需要在协议前加上@objc关键字;实现可选协议类型的时候,其属性、方法会变成可选类型;
协议的扩展:在协议中定义并实现方法,避免在多个实现类中实现多份同样的代码,或者是为了提供一个默认的实现;也可以使用where限制
范型
在类,方法,协议名称后紧跟一对尖括号,里面放上泛型名称。
类型约束:指定一个类型参数必须继承自指定类,符合一个特定的协议或者协议组合;也是放在尖括号中,使用冒号+类/协议名称的方式。
关联类型:定义协议的时候,预留一个类型占位符,待实现的时候通过typealias指定具体类型。这有点像C++中的typedef的味道
where语句:放在方法体前面,用来限定泛型类型满足某一个条件。
访问控制
需要从模块和源文件两个级别看,从高到低依次是:open,public,internal,fileprivate,private
自定义类型:
元组:分量中最严格的部分决定
函数类型:参数和返回值中最严格的部分决定函数的访问级别
枚举类型:不能单独指定枚举成员的访问级别
原始值和关联值:都不能低于枚举类型的访问级别
嵌套类型:private级别的类型中的嵌套类型,自动拥有private访问级别;public,internal级别的类型中的嵌套类型自动拥有internal访问级别;想要嵌套类型拥有public级别,需要显式指定
子类:不能高于父类;不过,可以通过重写继承来的任意类成员(方法,属性,构造器,下标)来将访问级别提高
成员的访问级别:常量,变量,属性,下标的访问级别不能高于类型本身
Getter&Setter:可以显式指定
构造器:默认构造器与类型的访问级别一致,但最高是internal;除非显示指定为public
协议:定义协议的时候指定访问级别也就是成员的,这一点不同于类类型;采纳了协议的类型最终的访问级别取定义类型的时候指定的类型和协议本身二者之中严格的那一个
扩展:对类,结构体,枚举类型进行扩展,默认是保持访问级别不变,但是可以显式指定来修改扩展成员的访问级别;通过扩展实现的协议,就不能显示指定访问级别类,会沿用协议本身的访问级别
泛型:取决于泛型类型、泛型函数本身的访问级别,再结合类型参数的类型约束
类型别名:不高于其表示的类型
高级运算符
运算符重载:二元符号,前缀prefix,后缀postfix,复合赋值运算符,等价,以及自定义运算符;这里会定义为类类型的成员。
Doc
[D.1] GitHub上的项目 swift-compiler-crashes 专注于swift编译器崩溃。
[D.2] 《The Swift Programming Language》中文版
[D.3] 最详尽的 Swift 代码规范指南
[D.4] 使用 guard 的正确姿势