Swift知识一览(一)

原创文章
声明:作者声明此文章为原创,未经作者同意,请勿转载,若转载,务必注明本站出处,本平台保留追究侵权法律责任的权利。
全栈老韩
全栈工程师,擅长iOS App开发、前端(vue、react、nuxt、小程序&Taro)开发、Flutter、React Native、后端(midwayjs、golang、express、koa)开发、docker容器、seo优化等。

一、概览

Swift是苹果主推的Apple系列产品的开发编程语言,相较于早期的Objective-C,Swift是一门类型安全的且具有可扩展性的编程语言。

Swift是开源的,apple在github上的开源Swift地址:https://github.com/apple/swift

Swift主要是使用C++编写。

swift的官网:https://www.swift.org/

如官网所说,Swift是一个对入门者友好,也对资深专家强有力支持的具有多用途的编程语言。它快捷、现代、安全且便于编写。

使用领域:

  1. Apple系列平台:比如iOS、macOS、ipadOS等其他平台;
  2. 跨平台命令行:因其语法简洁、安全且轻量;
  3. 服务端和网络:因其内存占用小,启动快、高性能。

目前由于Swift的ABI(Application Binary Interface,应用程序二进制接口)稳定,所以近期并没有新版本频繁更新。

目前最新的稳定版本是5.10。

解释:
ABI是应用程序与操作系统之间的底层接口,相关内容是目标文件格式、数据类型的大小/布局/对齐、函数调用约定等。
API是应用程序编程接口。

二、安装

2.1 MacOS系统

直接安装新版的Xcode,Xcode内置了新版的Swift。

2.2 Linux系统

使用Docker容器就可以获取到官方的Swift镜像。
Docker容器的基本知识可以在官网学习,也可以看我的博客:https://www.xinwei.ltd/article/83 查看基本命令。

2.3 Windows系统

使用包管理器,比如WinGet。

当在电脑中安装了Swift环境后,就可以在终端通过以下类似指令执行swift的操作。
swift -help.jpg

三、如何针对不同领域方向学习

3.1 命令行

可以参考官方教程:https://www.swift.org/getting-started/cli-swiftpm/

3.2 跨平台库

可以参考官方教程:https://www.swift.org/getting-started/library-swiftpm/

3.3 Web服务

可以参考官方教程:https://www.swift.org/getting-started/vapor-web-server/

3.4 iOS和macOS

可以参考官方教程:https://www.swift.org/getting-started/swiftui/

四、Swift的编译过程

可以通过在Xcode的Swift代码断点,查看汇编代码,Swift的编译前端是swiftc。以下图片可以揭示编译流程和编译前后端的概况。(以下图片来自于网络)
swift编译流程1
swift编译流程2

从图中可以看出,代码编译的过程主要分为以下几步:
1.生成语法树:swiftc -dump-ast main.swift
2.生成简洁的SIL代码:swiftc -emit-sil main.swift
3.生成LLVM IR代码:swiftc -emit-ir main.swift -o main.ll
4.生成汇编代码:swiftc -emit-assembly main.swift -9 main.s

五、基本语法和约定

5.1 类型声明

  • let 声明不可变变量
  • var 声明可变变量

5.1.1 常量

只能赋值一次,不要求在编译时期确定,但是使用之前必须赋值一次。

swift 复制代码
let num = 1

let num1: Int
num1 = 20

// 以下编译会报错
let a: Int
print(a)

5.1.2 标识符

可以使用任意字符作为常量名、变量名、函数名,但是不能以数字开头,也不能包含空白符、制表符、箭头等特殊字符。

swift 复制代码
let 🤭 = "emoj1"

5.2 数据类型

在Swift中有2种常见的数据类型

  • 值类型
  • 引用类型

5.2.1 值类型

包括以下:

  • 枚举enum、可选类型(本身也是枚举)
  • 结构体struct,包括常见的String、Array、Dictionary、Int、Float、Double、Bool、Character、Set

整型说明:
Int在32位平台上等价于Int32,在64位平台上等价于Int64。

浮点数说明:
Float有32位,精度只有6位
Double有64位,精度至少有16位

5.2.2 引用类型

class类型

5.2.3 字面量

其实就是直接确定的值
比如:

swift 复制代码
let num = 1
let str = "hello world"
let decimal = 0x01 // 16进制
let hex = 0o02 // 8进制
let binary = 0b03 // 2进制
let doubleHex = 0xFp2 // 16进制,表示15 * 2(^2),十进制的60
let doubleHex = 0xFp-2 // 16进制,表示15 * 2(^-2),十进制的3.75

// 数组
let arr = [1, 2, 3]
// 字典
let dic = ["name" : "小明", "age" : 10]

5.2.4 类型转换

swift 复制代码
let num: Int = 1
let num1: UInt8 = 2
let sum = num + Int(num1)

// 下面也是可以的,数字字面量本身没有明确类型
let sum = 1 + 3.14

5.2.5 元组(Tuple)

swift 复制代码
let response = (200, "success")
print("code is \(response.0),message is \(response.1))")

// 类似于前端的析构
let (code, msg) = response

let response1 = (code: 200, msg: "success")
print("code is \(response1.code),message is \(response1.msg))")

5.3 流程控制相关

5.3.1 if-else语句

if中的语句,只能是bool类型,而不能是像OC中1之类的数字

swift 复制代码
let num = 10
if num > 10 {
    // ...
} else if num < 10 {
    // ...
} else {
    
}

5.3.2 while语句

swift 复制代码
var num = 1
white num < 5 {
    num += 1
}

var age = 10
repeat { // 相当于do-while
    age += 1
} while age < 100

5.3.3 for循环

swift中的for循环,可以有for-in的写法,并且配合区间运算符,轻巧简洁

swift 复制代码
let nums = [1, 2, 3, 4, 5]
for num in 0...3 {
    print("num is \(nums[num])")
}

// 需要注意以下的循环
for var i in 0...3 {
    i += 1
    print(i)
} // 打印的是:1,2,3,4

// 可以省略迭代变量
for _ in 0...3 {
    // do something 4 times
    print("*")
}

5.3.4 区间运算符

  • 闭区间:
    0...3:相当于 >= 0,并且 <= 3之间的区间
    区间的类型是:ClosedRange<T>
  • 半开区间:
    0..<3:相当于 >= 0,并且 < 3之间的区间
    区间的类型是:Range<T>
  • 单侧区间
swift 复制代码
let minRange = ...10 // <=10以下的区间范围
let maxRange = 10... // >=10以上的区间范围

print(minRange.contains(5)) // true
print(minRange.contains(11)) // false

print(maxRange.contains(-1)) // false

区间的类型是:PartialRangeThrough<T>

这里解释以下类型中的T,后面会解释Swift中的这种泛型。

区间运算符也可以使用字符串,通过ASCII来判断值

swift 复制代码
let range = "a"..."f" // ClosedRange<String>
range.contains("c") // true
range.contains("z") // false

这里扩充一下,有一个函数,表示有间隔的区间(类似于步进器):

swift 复制代码
let start = 0
let interval = 2
let max = 100
for num in stride(from: start, through: max, by: interval) {
    print("100以内的正偶数:\(num)")
}// 0, 2, 4, 6, ..., 100

5.3.5 switch语句 & where

swift 复制代码
let num = 5
switch num {
    case 1: // 不允许写{}
        print("是1")
        break // 可以不写,也不会贯穿case
    case 2:
        print("是2")
        fallthrough // fallthrough可以实现条件贯穿
    default: // 不允许写{}
        print("非1非2")
}
  1. 针对switch中的表达式,需要case枚举尽,或者使用default。
    default在case穷举完之后,可以不使用default。
  2. case和default后面至少需要一条语句,或者break一下。
  3. case支持复合条件,或者case叠加
swift 复制代码
let char = "a"
switch char {
    case "a", "A":
        print("都是a")
    default:
        print("非a")
}

switch char {
    case "a":
    case "A":
        print("都是a")
    default:
        print("非a")        
}
  1. 可以使用区间或者元组
swift 复制代码
let num = 10
switch num {
    case 1...5:
        print("1<=x<=5以内")
    case 5<..10:
        print("5<x<=10以内")
    default:
        print("unknown")
}

let pt = (0, 10)
switch pt {
    case (0, 0):
        print("1")
    case (_, 10): // 下划线代表忽略对应位置的值
        print("纵轴是10的点")
    case (0, _):
        print("横轴是0的点")
    case (-1..<0, 10...20):
        print("在阴影区")                    
    default:
        print("unknown")
}

// 值绑定
let pt2 = (0, 10)
switch pt2 {
    case (let x, 0):
        print("横轴坐标\(x)")
    case (0, let y):
        print("纵轴坐标\(y)")
    case let (x, y):
        print("unknown point (\(x), \(y))")
}
  1. 配合where使用
swift 复制代码
let turple = (0, 10)
switch turple {
    case (let x, 10): // 绑定到x
        print("x is \(x), y is 10")
    case (0, let y): // 绑定到y
        print("y is \(y), x is 0")
    default:
        print("anything")
}

let turple1 = (0, 10)
switch turple1 {
    case let (x, y) where x == y: // where条件
        print("x is equal to y")
    default:
        print("anything")
}

let nums = [1, 2, 3, 4]
for num in nums where num > 2 { // 类似于continue
    print("num is \(num)")
}

5.3.6 标签语句

swift 复制代码
upper: for i in 0...3 {
    for j in 0...3 {
        if j > 3 {
            continue upper        
        }
        if i == 3 {
            break upper        
        }
        print("i is \(i), j is \(j)")
    }
}

5.4 函数

5.4.1 定义&声明

swift 复制代码
func dosomthing() {
    // code
}
// 等价于
func dosomething() -> Void {
    // code
}
// 等价于
func dosomething() -> () {
    
}

// 带参数
func dosomething(param1: Int, param2: Int) -> Int {
    return param1 + param2
}

// 隐式返回
// 如果整个函数体是一个单一表达式,那么函数会隐式返回这个表达式
func dosomething(a: Int, b: Int) -> Int {
    a + b
}
dosomthing(a: 1, b: 2) // 3

// 返回元组
func getValues(a: Int, b: Int) -> (sum: Int, average: Int) {
    let sum = a + b
    return (sum, sum >> 1)
}
let result = getValues(a: 10, b: 2)
print(result.sum) // 12
print(result.average) // 6

// 函数参数标签
func eat(thing food: String) {
    print("eat \(food)")
}
eat(thing: "苹果")

// 省略参数
func eat(_ food: String) {
    print("eat \(food)")
}
eat("苹果")

5.4.2 参数相关

swift 复制代码
// 默认参数值
func addUser(name: String = "小明", age: Int, score: Int = 100) {
    print("姓名:\(name),年纪:\(age),成绩:\(score)")
}
addUser(name: "Tom", age: 10, score: 80)
addUser(name: "Tom", age: 10)
addUser(age: 10, score: 80)
addUser(age: 10)

// 如果省略参数标签,此时应注意若,语义模糊,编译器编译时或运行时报错
func addUser(_ name: String = "小明", age: Int, _ score: Int = 100) {
    
}

// 可变参数,以print为例就是一个可变参数的函数
// 一个函数最多只能有1个可变参数
// 紧跟在可变参数后面的参数不可省略参数标签
func addNums(numbers: Int...) -> Int { // 此处的Int...表示一组Int参数
    var sum = 0
    for num in numbers {
        sum += num    
    }
    return sum
}
sum(numbers: 1, 2, 3, 4)

func sumTest(_ a: Int..., name: String, _ age: Int) {
    
}

// print函数的定义:
/// - Parameters:
/// - items: Zero or more items to print.
/// - separator: A string to print between each item. The default is a single space (`" "`).
/// - terminator: The string to print after all items have been printed. The
/// default is a newline (`"\n"`).
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

5.4.3 inout标记的参数地址传递

使用inout定义一个输入输出参数,可以在函数内部修改外部实参的值。

swift 复制代码
func swapValue(x: inout Int, y: inout Int) {
    // 第一种方式
    let temp = x
    x = y
    y = temp
    // 第二种方式,使用元组
    (x, y) = (y, x)
}

var x = 10, y = 20
swapValue(&x, &y)

注意:

  • 可变参数(比如:Int...)不能标记为inout
  • inout参数不能有默认值
  • inout参数只能传入可以被多次赋值的
  • inout参数的本质是地址传递(引用传递)

5.4.4 函数重载

定义:函数名相同,但参数个数不同,或者参数类型不同,或者参数标签不同。

swift 复制代码
func setPosition(x: Float, y: Float) -> (Float, Float) { }
func setPosition(x: Float, y: Float, z: Float) -> (Float, Float) { }

func setPosition(x: Float, y: Float) -> (Float, Float) { }
func setPosition(x: Int, y: Float) -> (Float, Float) { }

func setPosition(_ x: Float, _ y: Float) -> (Float, Float) { }
func setPosition(a: Int, b: Float) -> (Float, Float) { }

注意以下的代码编译器会报错(非重载,因为返回值的类型和函数重载无关):
非重载情况jpg

默认参数值和函数重载一起使用产生二义性时,编译器不会报错

swift 复制代码
func test(a: Int, b: Int) -> Int {
    a + b
}
func test(a: Int, b: Int, c: Int = 3) {
    a + b + c
}
test(a: 1, b: 2) // 调用的是第一个

注意如果可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器可能会报错。

5.4.5 内联函数 inline function

开启编译器优化(Release模式下默认开启),某些函数会被编译器编译为内联函数,也就是将函数的调用体展开执行。

(注意:如果函数体比较长,或者含有递归调用,或者含有动态派发,一般不会被自动内联)

一般情况下,Swift不需要手动处理内联条件。但是Swift也提供了手动处理内联函数的语义。

swift 复制代码
// 这种不会被内联,即使开启编译器优化
@inline(never) func test() {
    print("test")
}

// 开启编译器优化后,代码很长的情况,也会被内联。
// 但是如果有递归调用、动态派发,还是不会被内联。
@inline(__always) func test() {
    print("test")
}

5.4.6 函数类型

函数是有类型的,由形参类型、返回值类型组成

swift 复制代码
func dosomething() {} // 函数类型是:() -> Void 或者 () -> ()
// 另外提一句,Void是元组()的类型别名,点击Void进去或者看Swift源码可证

func add(a: Int, b: Int) -> Int {} //函数类型是:(Int, Int) -> Int

// 声明
var fn: (Int, Int) -> Int = add
fn(1, 2) // 3 这个时候不需要a或者b形参,因为函数类型不包括形参

5.4.7 函数作参数,或者作返回值

swift 复制代码
func add(a: Int, b: Int) -> Int {
    a + b
}
func minus(a: Int, b: Int) -> Int {
    a - b
}
func decideToHandle(_ fn: (Int, Int) -> Int, a: Int, b: Int) {
    let result = fn(a, b)
    print("result is \(result)")
}

// 作加法
decideHandle(add, a: 1, b: 2)
// 作减法
decideHandle(minus, a: 1, b: 2)

返回值如果是函数类型,这种函数叫作高阶函数(higher-order function)

swift 复制代码
func add(a: Int, b: Int) -> Int {
    a + b
}
func minus(a: Int, b: Int) -> Int {
    a - b
}
func handle(_ useAdd: Bool) -> (Int, Int) -> Int {
    useAdd ? add : minus
}

// 作加法
handle(true)(1, 2) // 3
// 作减法
handle(false)(1, 2) // -1

5.5 类型别名typealias

作用是用来给类型取别名的。
比如:

swift 复制代码
typealias MyInt = Int8
let num: MyInt = 4

typealias CustomFn = (Int, Int) -> Int
func add(a: Int, b: Int) -> Int {
    a + b
}
CustomFn = add
CustomFn(1, 2) // 3

5.6 嵌套函数

指的是在函数内部再定义函数。
如:

swift 复制代码
func handle(_ useAdd: Bool) -> (Int, Int) -> Int {
    func add(a: Int, b: Int) -> Int {
        a + b    
    }
    func minus(a: Int, b: Int) -> Int {
        a - b    
    }
    return useAdd ? add : minus
}
handle(true)(1, 2) // 3
handle(false)(1, 2) // -1

这是《Swift知识一览》系列的第一篇文章,若感兴趣,请关注后续发表的文章。

暂无评论,快来发表第一条评论吧