一、Swift编译流程
Swift前端使用swiftc
编译器做词法分析,后端使用LLVM
编译器,生成对应平台的二进制代码以及对二进制代码进行相应的优化
在C/OC中,前端编译使用的是clang
,后端也是LLVM
一开始是你自己编写的Swift代码【Swift Code】,然后再根据swiftc前端编译器生成语法树【Swift AST】,接下来再生成Swift特有的中间代码【Raw Swift IL】,再生成一个简洁的版本(Swift特有的中间代码)【Canonical Swift IL】。Swift代码不是一步到位变成二进制代码的,是有一个流程。中间代码生成完毕之后,转交给后端(LLVM),生成一个【LLVM IR】代码,它是LLVM的中间代码。LLVM编译器又会针对IR代码进行相应的优化。优化完毕之后,最终转成汇编代码【Assembly】,汇编代码最终变成二进制代码【Executable】。
总结流程为:Swift代码 -> 语法树 -> 中间代码 -> 转交给LLVM -> 汇编代码 -> 二进制代码
swiftc
存放在Xcode内部:Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
,同样clang
也在这个位置
swiftc
常用命令:
- 生成可执行文件
swiftc -o main.out main.swift
- 生成抽象语法树的命令(AST)
swiftc main.swift -dump-ast
- 生成中间语言(SIL)
swiftc main.swift -emit-sil
- LLVM中间表示层(LLVM IR)
swiftc main.swift -emit -ir
- 生成汇编语言
swiftc main.swift -emit-assembly
二、基础语法
常量
- 只能赋值一次
- 它的值不要求在编译时期确定,但使用之前必须赋值一次
- 常量、变量在初始化之前,都不能使用
标识符
- 标识符(常量名、变量名、函数名)几乎可以使用任何字符
- 限制:标识符不能以数字开头,不能包含空白字符、制表符、箭头等特殊字符
常见数据类型
- 值类型(value type):swift中除了类和闭包,都是值类型
- 引用类型(refrence type):类、闭包
整数类型
Int类型在32位平台为Int32,64位平台为Int64
整数的最值:UInt8.max、Int16.min
浮点类型
Float 32位,精度只有6位
Double 64位,精度至少15位
字面量
整数和浮点数可以添加额外的零或者添加下划线来增强可读性
100_0000、1_000_000.000_000_1、000123.456
- 类型转换
//整数转换
let int1 : UInt16 = 2_000
let int2 : UInt8 = 1
let int3 = int1 + UInt16(int2)//答案:2001
//浮点类型转换
let int = 3
let double = 0.14159
let pi = Double(int) + double //3.14159
let intPi = Int(pi) //3
//字面量可以直接相加,因为数字字面量本身没有明确的类型
let result = 3 + 0.1415926 // result类型推导为Double,值为3.1415926
元组
let http404Error = (404,"Not Found")
let (statusCode,StatusMessage) = http404Error
let (justStatusCode,_) = http404Error
let http200Status = (statusCode,200,description: "OK")
三、流程控制
if-else
- if后面的条件可以省略小括号
- 条件后面的大括号不可以省略(和OC不同,OC中单行表达式可以省略大括号)
- if后面的条件只能是Bool类型,因为swift没有非零即真的概念
while、repeat-while
- repeat-while相当于C语言中的do-while
- swift3开始,去除了++、--运算符,只能用+=1,-=1替代
for
- swift也没有C中的for(int i=0;i<5;i++),使用for(i in 0..<5)替代
- 遍历中i默认为let,可以显示声明为var,就可以在大括号中改变值使用了
- 区间运算符(...、..<,...number)
//闭区间运算符:a...b,等价于 a<=取值<=b
let range1 = 1...3//let range1: ClosedRange<Int>
//半开区间运算符:a..<b,等价于 a<=取值<b
let range2 = 1..<3//let range2: Range<Int>
//单侧区间:...a等价于,让区间朝着左侧尽可能远,最大不超过a; b...等价于,让区间朝着右侧尽可能远,最小不低于b
let range3 = ...5//let range3: PartialRangeThrough<Int>
let range4 = ..<5//let range: PartialRangeUpTo<Int>
let range5 = 5... //let range5: PartialRangeFrom<Int>
字符、字符串也可以使用区间运算符,但默认不能用在for-in中
let range = "cc"..."ff"//let range: ClosedRange<String>
range.contains("cb")//false
range.contains("ge")//false
range.contains("ef")//true
// \0囊括了所有可能要用到的ASCII字符
let characterRange : ClosedRange<Character> = "\0"..."~"
characterRange.contains("G")//true
带间隔的区间值
let hours = 11
let hourInterval = 2
//tickMark的取值:从4开始,累加2,不超过11
for tickMark in stride(from: 4, to: hours, by: hourInterval) {
print(tickMark)
}//4 6 8 10
switch
- case、default后面不能写大括号
- switch默认为不贯穿,不像C中必须加上break才是不贯穿,如果想要贯穿,在case的语句最后一句加上fallthrough
- switch必须保证能处理所有情况,也就是说,如果不能使用case列举出所有情况,那么必须有default
- case、default后面至少要有一条语句,如果不想做任何事,加个break即可
- swift的switch中不像C只能用int值,也支持Character、String类型、元组类型等
- switch的case也可以使用区间匹配(case 1...5),元组匹配(case (_,0)),这里的_表示忽略某个值,case匹配属于模式匹配(Pattern Matching)的范畴
- case中可以使用值绑定(用一个常量或变量来接收值),即(case (let x,var y))
- case中还可以使用where,即(case let(x,y) where x == y:)
for中也可以使用where来过滤(for num in numbers where num > 0)
数组其实可以使用filter函数来过滤,(numbers.filter($0 > 0)),这样就过滤得出大于0的所有成员的数组
语句的标签
- 标签语句一般用于多重循环或条件语句嵌套中,用于标记是结束哪一个循环
outer: for i in 1...4 {
for k in 1...4 {
if k == 3 {
continue outer
}
if i == 3 {
break outer
}
print("i == \(i), k == \(k)")
}
}
打印结果:
i == 1, k == 1
i == 1, k == 2
i == 2, k == 1
i == 2, k == 2
可以看出,没有k==3的打印,因为一旦执行到k==3直接跳到了外层的循环继续
四、函数
函数的定义
- 使用func关键字定义函数 func pi(num: Double) -> Double{}
- 形参默认是let,也只能是let
- 无返回值可以写为Void、()、或者直接不写
- 可以使用元组实现多返回值
隐式返回(Implicit Return)
- 如果函数体是一个单行表达式,那么函数会隐式返回这个表达式
func sum<T : FloatingPoint>(v1: T,v2: T) -> T{
v1 + v2
}
sum(v1: 10, v2: 20.0)//30
参数标签(Argument Label)
- 可以修改参数标签(添加外部名),默认外部名和内部名相同
- 可以使用下划线_ 省略外部名
func work(at time : String){
print("work at time \(time)")
}
work(at: "am 9:00")//work at time am 9:00
func sum<T: FloatingPoint>(_ v1: T,_ v2: T) -> T {
v1 + v2
}
sum(10, 20)//30
默认参数值
- 参数可以有默认值,如func sum(v1: Int = 10,v2: Int)
- C++的默认参数值有个限制,必须从右往左设置,但由于Swift拥有参数标签,因此并没有此类限制
- 有默认值的在函数调用时可不传入有默认值的参数,但是没有默认值的参数必须设置值
可变参数
- 一个函数最多只能有1个可变参数
- 可变参数在函数体内作为数组处理
- 紧跟在可变参数后面的参数不能省略参数标签
func test(_ numbers: Int...,string: String,_ other: String){
for i in numbers {
print(i)
}
print(string + other)
}
test(10,20,30, string: "这里的标签不能省略", "不是紧跟着就可以省略外部名")
Swift自带的print函数
- print(_ items: Any..., separator: String = " ", terminator: String = "\n")
- seperator参数表示用什么分隔 默认是空格
- terminator表示打印完了之后做什么,默认是\n换行
输入输出参数(In-Out Parameter)
- 可以用inout定义一个输入输出参数:可以在函数内部修改外部实参的值
- 可变参数不能标记为inout
- inout参数不能有默认值
- inout参数只能传入var变量
- inout参数的本质是地址传递(引用传递)
func swapValues(_ a: inout Int,_ b: inout Int){
/*
//不用中间变量的三种方法
//1.加减法 缺点:浮点数交换时可能会出现精度损失
a += b
b = a - b
a = a - b
//2.乘除法 缺点:也会出现精度损失,而且b还必须不能为0
a *= b
b = a/b
a = a/b
//3.异或法 缺点:只能完成整形变量的交换,对于浮点数无法完成交换
a ^= b
b ^= a
a ^= b
*/
//swift中因为有元组的存在
(a,b) = (b,a)
}
var num1 = 10,num2 = 20
swapValues(&num1, &num2)
print("num1 = \(num1),num2 = \(num2)")//num1 = 20,num2 = 10,外部实参的值被改变了
函数重载
- 定义: 函数相同 参数个数不同||参数类型不同||参数标签不同
- 返回值类型与函数重载无关
- 默认参数值和函数重载一起使用产生二义性时,编译器不会报错(在C++中会报错)
func sum(v1: Int,v2: Int) -> Int{
v1 + v2
}
func sum(v1: Int,v2: Int,v3: Int = 10) -> Int{
v1 + v2 + v3
}
let result = sum(v1: 1, v2: 2) //调用的是只有两个参数的
print(result)//3
内联函数
- 如果开启了编译器优化(release模式默认会开启优化),编译器会自动将某些函数变成内联函数.
XCode在debug下打开内联:Build Settings->搜索optimization->Optimization Level下debug选择Optimize for Speed - 将函数调用展开成函数体
- 在Release模式下,编译器已经开启优化,会自动决定哪些函数需要内联,因此没必要使用@inline
//永远不会被内联(即使开启了编译器优化)
@inline(never) func test() {
print("永不会内联")
}
//开启编译器优化后,即使代码很长,也会被内联(递归调用函数、动态派发的函数除外)
@inline(__always) func test(){
print("会被内联")
}
函数类型(Function Type)
func test(){} //()->Void 或者()->()
func sum(a: Int,b: Int) -> Int{
a + b
}//(Int,Int)->Int
//定义变量
var fn: (Int,Int) -> Int = sum
fn(2,3)//5 调用时不需要参数标签
- 函数类型可以作为函数参数
- 函数类型可以作为函数返回值,返回值是函数类型的函数,叫做高阶函数(Higher-Order Function)
typealias
- typealias用来给类型起别名
typealias Byte = UInt8
- 按照Swift标准库的定义,Void就是空元组()
public typealias Void = ()
嵌套函数(Nested Function)
嵌套函数就是讲函数定义在函数的内部
func forward(_ forward: Bool) -> (Int)->Int{
func next(_ input : Int) -> Int{
input + 1
}
func previous(_ input: Int) -> Int{
input - 1
}
return forward ? next : previous
}
forward(true)(3) //4
forward(false)(3) //2
五、枚举
枚举的定义
enum Direction{
case north
case south
case east
case west
}
enum Direction{
case north,south,east,west
}
上面两种定义方式等价
关联值(Associated Values)
enum Score{
case point(Int)
case grade(Character)
}
enum Date{
case digit(year: Int,month: Int,day: Int)
case string(String)
}
var date = Date.string("2020-05-22")
date = .digit(year: 2020, month: 5, day: 20)
switch date {
case .digit(let year,let month,var day):
day = 10
print(year,month,day) // 2020 5 10
case let .string(value):
print(value)
}
枚举定义时枚举变量后括号里面的就是关联值
原始值
枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做原始值
enum Grade : String{
case perfect = "A"
case great = "B"
case good = "C"
case bad = "D"
}
//原始值其实就是枚举变量的rawValue值
print(Grade.perfect.rawValue,Grade.great.rawValue,Grade.good.rawValue,Grade.bad.rawValue)//A B C D
注意:原始值不占用枚举变量的内存
隐式原始值(Implicitly Assigned Raw Values)
如果枚举的原始值类型是Int、String,Swift会自动分配原始值
- Int的原始值如果没有指定,那么默认第一个从0开始,依次加1递增,如果指定了,那么指定的枚举变量前面的仍然按照0开始,依次加1递增,指定枚举变量之后的在指定的基础上加1递增;
- String的原始值默认为枚举变量名的字符串
enum Direction : Int{
case north,south,east = 5,west
}
print(Direction.north.rawValue,Direction.south.rawValue,Direction.east.rawValue,Direction.west.rawValue)//0 1 5 6
enum Season : String{
case spring,summer,autumn = "test",winter
}
print(Season.spring.rawValue,Season.summer.rawValue,Season.autumn.rawValue,Season.winter.rawValue)//spring summer test winter
CaseIterable
枚举遵守CaseIterable协议可进行遍历
enum Direction : Int,CaseIterable{
case north,south,east = 5,west
}
let count = Direction.allCases.count
print(count)//4
for season in Direction.allCases{
print(season)
}
/*
north
south
east
west
*/
递归枚举(Recursive Enumeration)
- 递归枚举是一种枚举类型
- 有一个或多个枚举成员使用该枚举类型的变量作为关联值
- 在枚举成员前加上indirect来表示该成员可递归
enum ArithExpr{
case number(Int)
indirect case sum(ArithExpr,ArithExpr)
indirect case minus(ArithExpr,ArithExpr)
}
- 也可以定义枚举前加上indirect来让整个枚举成员在需要时可递归
indirect enum ArithExpr{
case number(Int)
case sum(ArithExpr,ArithExpr)
case minus(ArithExpr,ArithExpr)
}
应用案例
let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let minus = ArithExpr.minus(sum, two)
func calcuate(_ expr: ArithExpr) -> Int{
switch expr {
case let .number(value):
return value
case let .sum(left, right):
return calcuate(left)+calcuate(right)
case let .minus(left, right):
return calcuate(left) - calcuate(right)
}
}
calcuate(minus)//7
MemoryLayout
可以使用MemoryLayout获取数据类型占用的内存大小
enum Password{
case number(Int,Int,Int,Int)
case other
}
MemoryLayout<Password>.stride//40,分配占用的空间大小
MemoryLayout<Password>.size//33,实际用到的空间到校
MemoryLayout<Password>.alignment//8,对齐参数
var pwd = Password.number(9, 8, 6, 4)
pwd = .other
MemoryLayout.stride(ofValue: pwd)//40
MemoryLayout.size(ofValue: pwd)//33
MemoryLayout.alignment(ofValue: pwd)//8
enum TestEnum{
case t1(Double,Character),t2,t3
}
MemoryLayout<TestEnum>.stride//24
MemoryLayout<TestEnum>.size//24
MemoryLayout<TestEnum>.alignment//8