Swift基本语法

Swift简介

  • Swift是Apple在2014年6月WWDC发布的全新编程语言,中文名和logo是“雨燕”
  • Swift之父Chris Lattner
    • Clang编译器作者、LLVM项目的主要发起人
    • 从Apple离职后,先后跳槽到Tesla、Google
    • 目前在Google Brain从事AI研究

Swift版本

  • Swift5.x版本,ABI稳定
    • API(Application Programming Interface): 应用程序编程接口
      • 源代码和库之间的接口
    • ABI (Application Binary Interface):应用程序二进制接口
      • 应用程序与操作系统之间的底层接口
      • 涉及的内容有:目标文件格式、数据类型的大小、布局、对齐、函数调用约定等等
  • 随着ABI的稳定,Swift语法基本不会再有太大的改动
  • Swift完全开源: htps://github.com/apple/swift,主要采用C++编写

Swift编译流程

Swift Code --> Swift AST --> Raw Swift IL --> Canonical Swift IL --> LLVM IR --> Assembly --> Executable

swiftc

  • swiftc存放在Xcode内部
    • Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
  • 一些操作
    • 生成语法树:swiftc -dump-ast main.swift
    • 生成最简洁的SIL代码:swiftc -emit-sil main.swift
    • 生成LLVM IR代码: swiftc -emit-ir main.swift -o main.ll
    • 生成汇编代码: swiftc -emit-assembly main.swift -o main.s
  • 对汇编代码进行分析,可以真正掌握编程语言的本质

Playground - View

import UIKit
import PlaygroundSupport

let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
view.backgroundColor = UIColor.blue
PlaygroundPage.current.liveView = view

Playground - 注释

  • 单行注释 //
  • 多行注释 /* */
  • 多行嵌套注释 /* 1 /* 3 */ 2 */
  • 支持markup语法 添加冒号 //: 等

常量

  • 只能赋值一次
  • 它的值不要求在编译时期确定,但使用之前必须赋值一次
  • 变量、常量在初始化之前,都不能使用

标识符

  • 标识符(比如常量名、变量名、函数名)几乎可以使用任何字符
  • 标识符不能以数字开头,不能包含空白字符、制表符、箭头灯特殊字符

常见数据类型

  • 值类型 (value type)
    • 枚举(enum): Optional
    • 结构体(struct): Bool、Int、Float、Double、Character、String、Array、Dictionary、Set
  • 引用类型 (reference type)
    • 类(class)

字面量

  • 布尔类型 let bool = true
  • 字符串 let string = "Hello word!"
  • 字符(可储存ASCII字符、Unicode字符)let character: Character = "A"
  • 浮点数 let double = 12.0
  • 数组 let array = [1, 3, 3, 4]
  • 字典 let dictionary = ["age": 19, "height": 123]

元祖(tuple)

let http404Error = (404, "Not Found")
print("The status code is \(http404Error.0)")

let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")

let (justTheStatusCode, _) = http404Error

let http200Status = (statusCode: 200, description: "OK")
print("The status code is \(http200Status.description)")

流程控制

if-else

let age = 5
if age >= 22 {
	print("Get married")
} else {
	print("Just a child")
}	
  • if 后面的条件可以省略小括号,条件后面的大括号不能省略
  • if 后面的条件只能是Bool类型

while

  • repeat-while相当于C语言中的do-while

for

  • 闭区间运算符: a...b, a <= 取值 <= b
  • 半开区间运算符: a...<b, a <= 取值 < b
let names = ["Anna", "Lele", "Brian", "Jack"]
for i in 0...3 {
	print(names[i])
}		
let range = 1...3
for i in range {
	print(names[i])
}
for var i in 1...3 {
	i += 5
	print(i)
}
for _ in 1...3 {
	print("for")
}
for i in 0...<5 {
	print(names[i])
}	
  • 区间运算符用在数组上
  • 单侧区间:让区间朝一个方向尽可能的远
let names = ["Anna", "Lele", "Brian", "Jack"]
for i in names[0...3] {
	print(i)
}	
for i in names[...2] {
	print(i)
}

  • 带间隔的区间值
let hours = 11
let hourInterval = 2
for tickMark in stride(from: 4, through: hours, by: hourInterval) {
print(tickMark)
} 

switch

  • case、default后面不能写大括号{}
  • 默认可以不写break,并不会贯穿到后面的条件
  • 使用 fallthrough 可以实现贯穿效果
  • switch注意点
    • switch必须保证能处理所有情况
    • case、default后面至少要有一条语句
    • 如果不想做任何事,加个break即可
    • 如果能保证已处理所有的情况,可以不必使用default
    • case匹配属于模式匹配
  • switch也支持Character、String类型
  • switch 复合条件 case "Jack","Rose"
  • 区间匹配 case 0...3
  • 元祖匹配 case (0, 0)
  • 值绑定 case (let x, 0)只要求一方符合
  • where条件 case let (x, y) where x == y

标签语句

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)")
	}
}

函数

函数的定义

  • func pi() -> Double
  • 形参默认是let,也只能 是let
  • 无返回值 func pi()
  • 隐式返回 如果整个函数体是一个单一的表达式,那么函数会隐式返回这个表达式 省略return

返回元祖: 实现多返回值

func calculate(v1: Int, v2: Int) -> (sum: Int, difference: Int, average: Int) {
    let sum = v1 + v2
    return (sum, v1 - v2, sum >> 1) // >> 右移取平均值
}
let result = calculate(v1: 20, v2: 10)
result.sum     // 30
result.difference  // 10
result.average    //15

参数标签

  • 可以修改参数标签 func goToWork(at time: String){} at 用在函数里,是参数标签, time用在函数外
  • 可以使用 _ 省略参数标签 func goToWork(_ time: String){}

默认参数 (Default Parameter Value)

  • 参数可以有默认值 func check(name: String = "body") {}
  • C++ 的默认参数数值有个限制: 必须从右向左设置。由于Swift拥有参数标签,因此并没有此类限制
  • 在省略参数标签时,需要特别注意

可变参数(Variadic Parameter)

func sum(_ numbers: Int...) -> Int {
    var total = 0
    for number in numbers {
        total += number
    }
    return total
}
sum(10, 20, 30, 40) // 100
  • 一个函数最多只能有1个可变参数
  • 紧跟在可变参数后面的参数不能省略参数标签

print函数

  • public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

输入输出函数(In-Out Parameter)

  • 可以用inout定义一个输入输出函数: 可以在函数内部修改外部实参的值
  • 比如交换两个参数值
func swapValues(_ v1: inout Int, _ v2: inout Int) { (v1, v2) = (v2, v1) }
  • 可变参数不能标记inout
  • inout参数不能有默认值
  • inout参数本质是地址传递(引用传递)
  • inout参数只能传入可以被多次赋值的

函数重载(Function Overload)

  • 规则

    • 函数名相同
    • 参数个数不同 || 参数类型不同 || 参数标签不同
  • 注意点

    • 返回值类型与函数重载无关
    • 默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(c++ 报错)
    • 可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错

内联函数(Inline Function)

  • 如果开启了编译器优化(Release模式会默认开启优化 搜索 optimization), 编译器会自动将某些函数变成内联函数
    • 内联函数将函数调用展开成函数体
  • 哪些函数不会内联
    • 函数体特别长
    • 包含递归调用
    • 包含动态派发(即oc里的动态绑定)

函数类型(Function Type)

  • 每一个函数都是有类型的,函数类型由形参类型、返回值类型组成
  • 函数类型作为函数参数
  • 函数类型作为返回值(返回值是函数类型的函数,叫做高阶函数: Higher-Order Function)
  • typealias 用来给类型起别名 (按照Swift标准库定义, Voide就是空元祖 typealias Void = ())

嵌套函数

  • 将函数定义在函数内部

枚举

基本用法

enum Direction {
	case north, south, east, west
}

关联值(Associated Values)

  • 将枚举的成员值跟其他类型的关联储存在一起,会非常有用
enum Date {
    case digit(year: Int, month: Int, day: Int)
    case string(String)
}
var date = Date.digit(year: 2011, month: 9, day: 11)
date = .string("2019-10-9")
switch date {
case .digit(let year, let month, let day):
    print(year,month,day)
case let .string(value):
    print(value)
}
// let 也可以改为 var

原始值(Raw Value)

  • 枚举成员可以使用 相同累心 的默认值预先关联,这个默认值就叫做: 原始值 enum PokerSuit: Character { case spade = "A" }

隐式原始值(Implicitly Assigned Raw Values)

  • 如果枚举的原始值类型是Int、String,Swift会自动分配原始值
    • 如果是字符串,就是名字
    • 如果是Int,就是0、1、2
enum Direction : String {
	case north = "north"
	case south = "south"
	case east = "east"
	case west = "west"
}
enum Direction : String {
	case north, south, east, west
}
// 这两个枚举写法完全等价

递归枚举(Recursive Enumeration)

  • 在enum前必须加上indirect关键字 indirect enum AS {}

MemoryLayout

  • 可以使用MemoryLayout获取数据类型占用的内存大小
// 关联值 - 直接将传入的关联值存储在枚举变量的内存里
enum Password {
    case number(Int, Int, Int, Int)
    case other
}
var pwd = Password.number(5, 6, 4, 7)
pwd = .other
MemoryLayout<Password>.size //33,实际用到的控件大小--32个字节存储number,一个存储other
MemoryLayout<Password>.stride //40,分配占用的空间大小
MemoryLayout<Password>.alignment//8,对齐参数
// 原始值 - 和枚举成员绑定的,固定的,并不会将原始值存储到枚举变量内存里
enum Direction : Int {
    case north = 1, south, east, west
}
var d = Direction.north
MemoryLayout<Direction>.size //1
MemoryLayout<Direction>.stride //1
MemoryLayout<Direction>.alignment//1

可选项(Optional)

  • 可选项,一般也称为可选类型,它允许将值设置为nil
  • 在类型名称后加一个?来定义一个可选项
	var age: Int? //表示的是初始值默认是nil

强制解包(Forced Unwrapping)

  • 可选项是对其他类型的一层包装,可以将它理解为一个盒子
    • 如果为nil,那么他就是个空盒子
    • 如果不是nil,那么盒子里转的是被包装类型的数据
  • 如果要从可选项中取出被包装的数据(将盒子里的东西取出来),需要使用感叹号!进行强制解包
  • 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误

可选项绑定(Optional Binding)

  • 可以使用可选项绑定来判断可选项是否包含值
    • 如果包含就会自动解包,把值赋给一个临时的常量或者变量,并返回true,否则返回false
if let number = Int("123") {
    print("\(number)")
} else {
    print("shibai")
}
  • 可选项绑定,多个条件要用","区别开来
if let number = Int("123"), let s = Int("11") {
    print("\(number)")
} else {
    print("shibai")
}
  • while循环中使用可选项绑定
// 遍历数组,将遇到的正数加起来,如果遇到负数或者非数字,停止遍历
var strs = ["10", "20", "abc", "-20", "30"]

var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
    sum += num
    index += 1
}
print(sum)

空合并运算符(Nil-Coalescing Operator)

  • a ?? b
    • a 是可选项
    • b 是可选项,也可以不是
    • b 和 a 的存储类型必须相同
    • 如果 a 不为nil,就返回a
    • 如果a 为nil,就返回b
    • 如果b 不是可选项, 返回a 时会自动解包
let a: Int? = 7
let b: Int = 2
let c = a ?? b // c 是 Int, 1
  • a ?? b ?? c 从左往右计算
  • ?? 和 if let 配合使用 if let c = a ?? b {}

guard语句

  • guard 条件 else
    • 当guard语句的条件为false时,就会执行大括号里的代码
    • 当guard语句的条件为true时,就会跳过guard语句
    • guard语句特别适合做提前退出
  • 当使用guard语句进行可选项绑定时,绑定的常量、变量也能在外层作用域中使用
// 使用 guard else 实现用户登录
func login (_ info: [String : String]) {
    guard let username = info["usernmae"] else {
        print("请输入用户名")
        return
    }
    guard let password = info["password"] else {
        print("请输入密码")
        return
    }
    print("用户名: \(username)", "密码: \(password)", "登录中...")
}

隐式解包(Implicitly Unwrapped Optional)

  • 在某些情况下,可选项一旦被设定值之后,就会一直拥有值,在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
  • 可以在类型后面加个感叹号!,定义一个隐式解包的可选项
// 隐式解包的可选项
let num1: Int! = 10
let num2: Int = num1

字符串插值

  • 可选项在字符串插值或者直接打印时,编译器会发出警告 var age: Int? = 10; print("My age is \(age)")

  • 三种消除警告的方法

    • print("My age is \(age!)")
    • print("My age is \(String(describing: age))")
    • print("My age is \(age ?? 0)")

多重可选项

var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
print(num2 == num3) // true

var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
print(num1 == num3) // false
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1
  • 可以使用lldb指令 frame variable -R 或者 fr v -R 查看区别

多重可选项

var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 10
print(num2 == num3) // true

var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
print(num2 == num3) // false
print(num1 == num3) // false
(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1
  • 可以使用lldb指令 frame variable -R 或者 fr v -R 查看区别
var num1: Int? = 10
var num2: Int?? = num1
var num3: Int?? = 20
/*
(lldb) fr v -R num1
(Swift.Optional<Swift.Int>) num1 = some {
  some = {
    _value = 10
  }
}
(lldb) fr v -R num2
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
  some = some {
    some = {
      _value = 10
    }
  }
}
(lldb) fr v -R num3
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = some {
  some = some {
    some = {
      _value = 20
    }
  }
}
*/
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil
/*
(lldb) fr v -R num1
(Swift.Optional<Swift.Int>) num1 = none {
  some = {
    _value = 0
  }
}
(lldb) fr v -R num2
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
  some = none {
    some = {
      _value = 0
    }
  }
}
(lldb) fr v -R num3
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = none {
  some = some {
    some = {
      _value = 0
    }
  }
}
*/

可选项的本质

  • 本质是 enum 类型
enum Optional<Wrapped>: ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
    public init(_ value: Wrapped) {
        
    }
}
var age: Int? = 10
// 等价于
var age: Optional<Int> = .some(10)
var age: Int? = 20

switch age {
case let v:
    print(v)
default:
    break
}

// 如果 age = nil 那么,这个 case let v? 就不会执行
switch age {
case let v?: // 会解包
    print(v) 
default:
    break
}

// 其实就相当于
switch age {
case let .some(v):
    print(v)
default:
    break
}

--------------------------执行结果--------------------------
Optional(20)
20

posted @ 2021-04-12 16:10  YALMiOS  阅读(242)  评论(0)    收藏  举报