一、Swift編譯流程
Swift前端使用swiftc
編譯器做詞法分析但狭,后端使用LLVM
編譯器披诗,生成對應平臺的二進制代碼以及對二進制代碼進行相應的優(yōu)化
在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代碼進行相應的優(yōu)化。優(yōu)化完畢之后沿后,最終轉成匯編代碼【Assembly】,匯編代碼最終變成二進制代碼【Executable】沿彭。
總結流程為:Swift代碼 -> 語法樹 -> 中間代碼 -> 轉交給LLVM -> 匯編代碼 -> 二進制代碼
swiftc
存放在Xcode內部:Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
,同樣clang
也在這個位置
swiftc
常用命令:
- 生成可執(zhí)行文件
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摧阅,就可以在大括號中改變值使用了
- 區(qū)間運算符(...汰蓉、..<,...number)
//閉區(qū)間運算符:a...b,等價于 a<=取值<=b
let range1 = 1...3//let range1: ClosedRange<Int>
//半開區(qū)間運算符:a..<b,等價于 a<=取值<b
let range2 = 1..<3//let range2: Range<Int>
//單側區(qū)間:...a等價于,讓區(qū)間朝著左側盡可能遠棒卷,最大不超過a; b...等價于古沥,讓區(qū)間朝著右側盡可能遠,最小不低于b
let range3 = ...5//let range3: PartialRangeThrough<Int>
let range4 = ..<5//let range: PartialRangeUpTo<Int>
let range5 = 5... //let range5: PartialRangeFrom<Int>
字符娇跟、字符串也可以使用區(qū)間運算符,但默認不能用在for-in中
let range = "cc"..."ff"http://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
帶間隔的區(qū)間值
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也可以使用區(qū)間匹配(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的所有成員的數組
語句的標簽
- 標簽語句一般用于多重循環(huán)或條件語句嵌套中吧碾,用于標記是結束哪一個循環(huán)
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的打印,因為一旦執(zhí)行到k==3直接跳到了外層的循環(huán)繼續(xù)
四倦春、函數
函數的定義
- 使用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
內聯函數
- 如果開啟了編譯器優(yōu)化(release模式默認會開啟優(yōu)化),編譯器會自動將某些函數變成內聯函數.
XCode在debug下打開內聯:Build Settings->搜索optimization->Optimization Level下debug選擇Optimize for Speed - 將函數調用展開成函數體
- 在Release模式下布轿,編譯器已經開啟優(yōu)化哮笆,會自動決定哪些函數需要內聯,因此沒必要使用@inline
//永遠不會被內聯(即使開啟了編譯器優(yōu)化)
@inline(never) func test() {
print("永不會內聯")
}
//開啟編譯器優(yōu)化后汰扭,即使代碼很長稠肘,也會被內聯(遞歸調用函數、動態(tài)派發(fā)的函數除外)
@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