前言
阅览耗时15分钟
参考资料:
Swift教程
官网Swift介绍
中文翻译手册
官方手册
Swift编译流程
目录
一、Swift
时间线
- 2014年6月3日,苹果在WWDC开发者大会发布Swift
- 2014年8月18日,Swift 1.0
- 2014年10月16日,Swift 1.1
- 2015年4月8日,Swift 1.2
- 2015年9月16日,Swift 2.0
- 2015年10月20日,Swift 2.1
- 2015年12月4日,苹果宣布Swift编程言语开放源代码
- 2016年3月21日,Swfit 2.2
- 2016年9月13日,Swift 3.0
- 2016年10月27日,Swift 3.0.1
- 2017年3月27日,Swift 3.1
- 2017年9月19日,Swift 4.0
- 2018年3月29日,Swift 4.1
- 2018年9月17日,Swift 4.2
- 2019年3月25日,Swift 5.0
- 2019年9月10日,Swift 5.1
- 2020年3月24日,Swift 5.2
- 2020年9月16日,Swift 5.3
- 2021年4月26日,Swift 5.4
- 2021年9月20日,Swift 5.5
- 2022年3月14日,Swift 5.6
- 2022年9月12日,Swift 5.7
按照苹果的节奏,下次更新将会在2023
年3
月份。以上各个版别的差异能够看Swift官方手册
现在已经是XCode 14
了,我用的是XCode 11
, 本文介绍的语法,都支撑的。或许快速简略了解能够在线运转试试在线运转Swift
有条件的能够运用XCode
,玩玩Playground,能够实时显示结果,包扩图形、列表。
编译流程
详细参考
swift code -> swift ast -> raw swift IL -> Canonical Swift IL ->LLVM IR ->Assembly -> Executable
生成语法树 swiftc -dump-ast main.swift -o main.ast
生成最简练的SIL代码 swiftc -emit-sil main.swift
生成LLVM IR代码 swiftc -emit-ir main.swift -o main.ll
生成汇编代码 swift -emit-assembly main.swift -o main.s
二、根本语法
常量与变量
import Cocoa
//常量
let maxNum = 1000
//变量
var index = 0
基础类型操作
//1. 声明一个String类型
var test: String = "xxx"
//等价于
var test2 = "xxx"
//Int Double String Float
//2. 不同类型的表明办法 十进制、二进制、八进制等
var decimalInt:Int = 17
var binaryInt: Int = 0b10001
let octalInt:Int = 0o21
//3.
let float1 = 0.012
//等价于
let float2 = 1.2e-2
//4.
let bignum = 1000000
//等价于
let bignum2 = 1_000_000
//5. boolean类型
let testBool = true
if testBool
{
println("I'm true")
}
赋值问题
let float_num: Float = 1
let num_int:Int = 1.2// --> 转化后是1,所以界说的时分按规范来
let a:Int = 3
let b:Double = 0.14
//let sum:Double = a + b //cannot invoke '+' with an argument list of type(int ,Double)
let sum:Double = Double(a) + b
元组
fallthrough
配合switch
运用,Swift
的switch
句子默许在每个case
履行完后主动break
,不像其他言语,必须写break
,想要多个case
都履行到,就需求运用fallthrough
。
可是对于case
中运用了let
句子的语法,不允许运用fallthrough
let tupple1 = (true, "hello", "world")
//经过元组匹配来拜访
let (a , b ,c ) = tupple1
//或许经过以下方式来拜访,能够理解为下标
tupple1.0
tupple1.1
tupple1.2
let tupple2 = (x: true, y:"hello", z:"world")
tupple2.x
tupple2.y
tupple2.z
var coordinate = (1, 1)
switch coordinate
{
case (0,0):
println("0,0")
case (1,1):
println("1,1")
fallthrough //接下来会进入到下个case的判别中
case (-2..2, -2..2):
println("在这个区间内")
case (let x, 0):
println("x,0坐标射中")
case let(x, y) where x == y:
println("x,y坐标,x和y持平")
//fallthrough 这儿不能写,写了会报错,语法不允许
default:
println("default")
}
Option
由于Swift
是强类型言语,必然会和Rust
或许KT
相同,有可选型,对空就行处理
拜访值的时分需求加!
,或许直接运用if let
句子解包
var options1:Int? = 12
let userInput = "abc"
var age = userInput.toInt() //要么是nil要么是int, 此刻是nil
if(age != nil){
println("age is \(age!)")
}
//等价于
if let age = userInput.toInt()
{
println("age is \(age)")
}
表达式
var a:Int?
a = 10
var b = 20
var c = a != nil ? a! : b//a不为空,有值,则为a的值a!,否则b
//等价于
var c = a ?? b
区间运算符
a..b //对应 [a,b]
a..<b//对应 [a,b)
for index int 1..10
{
index //输出 1 到 10
}
for i in -99...99
{
i * i
}
字符串
终于能够直接拼接了,用习惯了Java
var s = ""
var str = String()
var ch:Character = "."
str.append(ch)
var str2 = "test"
str += str2
countElements(str)//str的长度
let str_a = "abc"
let str_b = "abc"
str_a == str_b
var str = "welcome to study swift! First Course is Hello World!"
let startIndex:String.Index = str.startIndex
let endIndex:String.Index = advance(str.startIndex, 10)
let searchRange = Range<String.Index>(start:startIndex, end:endIndex)
//从 0 到 10 之间 找到World,回来Option
str.rangeOfString("World", options:NSStringCompareOptions.CaseInsensitiveSearch, range:searchRange)
数组
var array = ["A", "B"]
var array1 = [Int]()
var array2 = Array<String>()
for (index, item) in enumerate(array)
{
println("\(index) - \(item)")
}
//二维数组
var board = Array<Array<Int>>()
for i in 0..10{
board.append(Array(count:10, repeatedValue:0))
}
var i = 0, j = 0
mainloop: for i = 0;i < 10;i ++
{
for j = 0;j < 10; j++
{
if board[i][j] == 1
{
break mainloop //这块也是一个语法糖,直接回到了最外层
}
}
}
字典
var dict = [1:"A", 2:"B"]
var dict2 = [Int:String]()
var dict3 = Dictionary<Int,String>()
if( dict[3] == nil){
println("is nil")
}
for (key, value) in dict
{
println("\(key): \(value)")
}
for key in dict.keys
{
println(key)
}
for value in dict.values
{
println(value)
}
Set
无序数据调集
var setDemo = Set<String>()
var str = "name"
if !setDemo.contains("test"){
setDemo.insert(str)
}
var B:Set<Int> = [2, 3, 4]
var C = Set<Int>([2,3,4, 6, 8])
B.intersect(C) //结果是[2,3,4] 取两者交集
B.union(C) //取的是[2,3,4,,6,8]
B.subtract(C) //[]
B.exclusiveOr(B)//异或操作 [6,8]
B.isSubsetOf(C)//B调集是不是C调集的子集,回来true
//怎么界说一个元素是不同的值
var arr:[AnyObject] = ["test", 1, 2]
过错处理
自界说过错
enum VendingMachineError: Error {
case invalidSelection //挑选无效
case insufficientFunds(coinsNeeded: Int) //金额缺乏
case outOfStock //缺货
}
经过throws
抛过错
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
....
print("Dispensing (name)")
}
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
经过do-catch
处理过错
//语法如下
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
//如
do {
//测验履行办法
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional (coinsNeeded) coins.")
} catch {
print("Unexpected error: (error).")
}
假如想完成其他言语里try-catch-finally
在finally
里做资源开释等处理的话,能够运用defer
关键字
defer
句子将代码的履行延迟到其时的效果域退出之前
//写在任何办法体内,捕获异常前
defer{
close(file)
}
三、办法
有回来值
func sayHello(name: String?) -> String
{
let result = "Hello, " + (name ?? "Guest") + "!"
return result
}
无回来值
func say(){ }
有多个回来值
回来多个值
func test2(data:[Int]) -> (max:Int, min:Int)?{
if data.isEmpty
{
return nil
}
retrun (1,1)
}
//运用
var data:[Int]? = [1,2,3,4,5]
data = data ?? [] //假如data为nil,则新建一个空数组
if let result = test2(data!) //回来的数据已经解包了
{
println("ok!")
}
办法的参数运用
//1.在声明的时分加上#,阐明外部参数名和内部参数名相同。 假如不加#,例如age 便是外部参数名,myage便是内部参数名。这种用法只是为了更加语义化,方便维护
func sayHello(#nickname:String, age myage:Int) -> String{
let result = nickname + " \(myage)"
return result
}
sayHello(nickname: "xxx", age: 10)
默许值
有默许值的参数,假如不指定外部参数名,那么外部参数名 == 内部参数名
非默许值的参数一定要放在前面,并且按次序,否则编译器识别不出来。
fun sayHello2(nickage:String, greeting:String = "Hello") -> String
{
return "ok"
}
sayHello2("xxxx")
sayHello2("xxxx", greeting: "Good")
fun sayHello2(nickage:String, _ greeting:String = "Hello") -> String
{
return "ok"
}
sayHello2("xxxx","Good")
可变参数
可变参数
func add(a:Int, b:Int, other:Int ...) -> Int{
var result = a + b
for number in other
{
result += number
}
return result
}
var res = add(2,3)
res = add(2, 3, 4, 5)
变量参数
咱们之前的办法都是默许值传递的,参数默许是常量参数,不能被办法内部运用的
func changeValue(num:Int) {
num = 2 //不能改的,报错
}
func changeValue2(var num:Int){
num = 3
}
值传递
var num = 4;
changeValue2(num)
num //仍是4 ,不会改变值
func tryToChangeValue(var x:Int){x++}
var a:Int = 2
tryToChanageValue(a)
a //仍是2
引证传递
func changeValue3(inout a: Int){
a = 6
}
var x = 4
changeValue3(&x)
x //此刻便是6了
函数作为回来值
这个功能大多数言语都是支撑的,在开发C++
项目中,其时都看懵圈了。主张注释API
搞起来。
func testFree1(weight:Int )->Int
{
return weight;
}
func testFree2(weight:Int )-> Int
{
return 2*weight
}
func choose(weight:Int ) -> (Int) -> Int
{
return weight <= 10 ? testFree1 : testFree2
}
闭包
func compare(a:Int, b:Int) -> Bool{
return a > b
}
var arr:[Int] = [1,3,5,7,9]
sorted(arr, compare)
//等价于(sorted后面这个便是闭包)
sorted(arr, {a:Int, b:int} -> Bool in
return a > b
})
sorted(arr, {a, b in return a>b})//能够主动会推导出入参和回来值
sorted(arr, {a, b in a > b}) //也能够简写成这样,去掉参数类型界说和return
sorted(arr,{$0 > $1})//$o代表第一个参数,$1代表第二个参数
sorted(arr , > )//甚至能够缩写成这样,可是不主张
函数引证类型
闭包+函数的结合体,回来的是函数
func calc(today: Int) -> () -> Int{
var total = 0;
return {total += today; return total;}
}
var daily = calc(2);
daily()//结果是2
daily()//结果是4
var todayPlan = daily
todayPlan() //结果是6
daily() //结果是10
内联函数
func test(){
println("test")
}
test()//申请栈空间,用完开释
release直接优化成
println("test")//避免申请了占空间,debug默许没有优化,在XCode中自己设置
函数体比较长的时分,递归调用的时分,动态派发的时分
永远不了内联
@inline(never) func test(){
println("test")
}
@inline(__always) func test(){
println("test")
}
四、类与结构体、枚举
界说相似,和其他言语都相似
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
//结构器
init(){}
init(resolution resolution: Resolution){
selft.resolution = resolution
}
init(frameRate: Double){
selft.frameRate = frameRate
}
init(_ name: String){
selft.name = name
}
}
怎么拜访?(点语法)
//创立
let someResolution = Resolution()
let someVideoMode = VideoMode()
//结构体拜访
someResolution.width
//类拜访
omeVideoMode.resolution.width
//创立2
let resolution1 = Resolution(width:180, height:220)
//创立3
let someVideoMode1 = VideoMode(resolution:someResolution)
let someVideoMode2 = VideoMode(frameRate:22.2)
let someVideoMode3 = VideoMode("Hello Vide Mode")
咱们需求留意枚举和结构体都是值类型,也便是赋值的时分,会拷贝内部数据给对方,可是目标却不是同一个
let resolution1 = Resolution(width:180, height:220)
var resolution2 = resolution1
resolution2.width = 330
print("resolution1 is now (resolution1.width) pixels wide")
// 打印 "resolution1 is now 180 pixels wide"
print("resolution2 is now (resolution2.width) pixels wide")
// 打印 "resolution2 is now 330 pixels wide"
可是类是引证类型的,和上面相反
let testVideo = VideoMode()
testVideo.resolution = resolution1
testVideo.interlaced = true
testVideo.name = "test"
testVideo.frameRate = 211.1
let testVideo2 = testVideo
testVideo2.frameRate = 66.6
print("The frameRate property of testVideo is now (testVideo.frameRate)")
// 打印 "The frameRate property of testVideo is now 66.6"
指定结构器和便当结构器
便当结构器来调用同一个类中的指定结构器,并为部分形参提供默许值
class Food {
var name: String
//指定结构器
init(name: String) {
self.name = name
}
//便当结构器
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat 的姓名是 "Bacon"
let mysteryMeat = Food()
// mysteryMeat 的姓名是 [Unnamed]
子类重写遍历结构器
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
//`RecipeIngredient`会主动承继父类的所有便当结构器。这儿重写了父类的指定结构器
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
可失利的结构器,便是加个?
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
延迟加载
使用关键字lazy
润饰,只要第一次用到才会去加载
留意,大局的常量或变量都是延迟核算的,跟延时加载存储特点相似,不同的当地在于,大局的常量或变量不需求标记lazy
润饰符。
class DataManager {
lazy var importer = DataImporter()
var data = [String]()
// 这儿会提供数据管理功能
}
Getter、Setter
和OC
相同,点语法调用的是get、set
,可是结构体里还能这样写,我也是没有想到,就和Swift
的办法能持续嵌套办法相同,出人意料,处处都是语法糖
看下面这个比如首要是center
欠好存储值,是核算出来的
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at ((square.origin.x), (square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”
上面还能够持续简化语法
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
假如center
特点,你想改成只读,那么能够这样修正
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
return Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
}
类型特点
类静态变量,Swift
叫做类型特点
某个类型关联的静态常量和静态变量,是作为global
(大局)静态变量界说的。可是在 Swift
中,类型特点是作为类型界说的一部分写在类型最外层的花括号内,因而它的效果范围也就在类型支撑的范围内。
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
//`class`关键字可用来支撑子类对父类的完成进行重写
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
//拜访
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”
实例办法
class Counter {
var count = 0
func increment() {
count += 1 //等价于self.count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
// 初始计数值是0
counter.increment()
// 计数值现在是1
counter.increment(by: 5)
// 计数值现在是6
counter.reset()
// 计数值现在是0
可变的实例办法
之前说到,结构体和枚举是值类型,赋值的时分,只是将值赋值给新的目标。在实例办法中,值类型的特点不能被修正。除非加上mutating
关键字
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at ((somePoint.x), (somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”
下面这种,使用self
写法,将会产生全新的实例
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
类型办法
在 Swift
中,你能够为所有的类、结构体和枚举界说类型办法。每一个类型办法都被它所支撑的类型显式包含。理解为静态办法即可。
class SomeClass {
class func someTypeMethod() {
// 在这儿完成类型办法
}
}
SomeClass.someTypeMethod()
类承继
和OC
相同,特点和办法都会从父类承继过来。
语法如下
class A{
var currentPrice = 11.1
var description: String{
return "hello world! price is \(currentPrice) from A"
}
func makeNoise(){
}
//下面的办法不能重写
final func father(){
println("called father func")
}
}
class B: A{
//重写父类特点
override description:String{
return super.description + " son can be free"
}
//重写父类办法
override func makeNoise(){
println("make nosise start....")
}
}
扩展
和KT相同,能够为一个类增加一个扩展办法,也和OC
的协议相似。
扩展详解
extension SomeType {
// 在这儿给 SomeType 增加新的功能,各种办法,包括结构函数
func xxx(){}
}
协议
和OC
相同,只是不会在前面加@
了
protocol SomeProtocol {
// 这儿是协议的界说部分
var mustBeSettable: Int { get set } //可读可写
var doesNotNeedToBeSettable: Int { get }//可读
static var someTypeProperty: Int { get set }
func random() -> Double
static func someTypeMethod()
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 这儿是结构体的界说部分
var mustBeSettable: Int
var doesNotNeedToBeSettable: Int
static var someTypeProperty: Int
func random() -> Double{
println("实例办法")
return 2.2
}
static func someTypeMethod(){
println("类办法")
}
}
泛型就不说了,和其他言语相同的概念,便是加个占位符,完成几乎相同。
你或许还会对润饰符有疑问,Swift
润饰符默许为internal
(同一模块源文件中都可拜访),总共有open
、public
、internal
、fileprivate
、private
五种。见拜访等级
析构
Swift
会主动开释不需求的资源,也是经过ARC
来进行管理的。同样对应调用的办法是
deinit{
//履行析构
}
类引证
class Person{
weak let person: Person? //弱引证
}
let p = Person();//强引证
无主引证
弱引证不同的是,无主引证在其他实例有相同或许更长的生命周期时运用。你能够在声明特点或许变量时,在前面加上关键字unowned
表明这是一个无主引证。首要使用来处理循环引证过程中,这个目标不是nil的引证资源开释。
unowned let father: Father //例如你一定有父级
枚举
enum Direction{
case East
case West
case South
case North
}
var direct:Direction
switch direct
{
case .East:println("your direction is East")
case .West:println("your direction is West")
case .South:println("your direction is South")
case Direction.North:println("your direction is North")
}
enum Month: Int{
case Jan=1, Feb, Mar, Apr, May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
}
let curMonth:Month = .Nov
curMonth.rawValue //11
let nextMonth:Month? = Month(rawValue: 12)
nextMonth!.rawValue
//原始值,关联的是Char类型,叫做关联枚举
enum Vowel:Character{
case A = "a"
case E = "e"
}
enum Code{
case QR(Int, Int, Int)
case PC(String)
}
let codeA = Code.Pc("https://www.xxx.com")
let codeB = Code.QR(1, 2, 3)
swtich codeA
{
case Code.QR(let a, let b, let c):
println("value is \(a) , \(b) , \(c)")
case Code.PC(let str):
println("value is \(str)")
}
//递归枚举
indirect enum ArithExpr{
case number(Int)
case sum(ArithExpr, ArithExpr)
case difference(ArithExpr ,ArithExpr)
}
枚举内存
enum Month: Int{ case Jan=1, Feb, Mar, Apr, May,Jun,Jul,Aug,Sep,Oct,Nov,Dec }
enum Vowel:Character{ case A = "a" case E = "e" }
这种枚举只会占一个自己,只需求记载是哪个case
。
enum Code{ case QR(Int, Int, Int) case PC(String) }
这种枚举占40
个字节,4
个Int
32
个字节存储,然后剩余的case
记载需求1
个字节,所以实际占用33个字节,可是要8
字节对齐,所以占用内存40
个字节