Swift5 基础教程与进阶合集
一、 可选项(Optional)
定义
- 可选项,一般也叫可选类型,它允许将值设置为nil
- 在类型名称后面加个问好?来定义一个可选项
强制解包(Forced Unwrapping)
- 如果要从可选项中取出被包装的数据,需要使用感叹号!进行强制解包
- 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误
var a : String?
a!//error: Fatal error: Unexpectedly found nil while unwrapping an Optional value
可选值绑定(Optional Binding)
- 可以使用可选项绑定类判断可选项是否包含之,如果包含就自动解包,把值给一个临时的常量(let)或者变量(var),并返回true,否则返回false
- 可选绑定可以在if、while、switch-case、guard中
if let number = Int("123"){
print("字符串转换整形成功:\(number)") //字符串转换整形成功:123
} else {
print("字符串转化整形失败")
}
enum Season : Int{
case Spring = 1, summer,autumn,winter
}
var season : Season? = .summer
/*
这里可以用var来接收,表示可修改
可以使用同名变量接收
*/
if var season = season {
season = .winter
print(season.rawValue)//4
}
多个可选绑定
- 多个可选绑定可写在一排,用逗号间隔开,并且还可以加上逻辑表达式,它们的关系是一个逻辑与(&&)的关系
if let first = Int("4"),let second = Int("5") , first < second,second < 10{
print("成功了,first:\(first),second:\(second)")//成功了,first:4,second:5
}
空合运算符??(Nil-Coalescing Operator)
public func ??
- a ?? b 相当于 a != ni ? a! : b
- a是可选项,b是可选项或者不是可选项
- b跟a的存储类型必须相同
- 如果a不为nil,就返回a!,如果a为nil,就返回b
- 如果b是可选项,返回a时会自动解包
guard语句
guard 条件 else {
//do something
退出当前作用域
//return、break、continue、throw error
}
- 当guard语句的条件为false时,就会执行大括号里面的代码
- 当guard语句的条件为true时,就会跳过guard语句执行后面的语句
- guard特别适合用来提前退出
- 当使用guard语句进行可选绑定时,绑定的常量(let)、变量(var)如果绑定成功,可以在guard后面的语句中使用
隐式解包(Implicitly Unwrapped Optional)
- 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
- 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
- 可以在类型后面加个感叹号!定义一个隐式解包的可选项
let num : Int! = 10
print(num + 1)//11
可选项字符串插值
- 可选项在字符串插值或者直接打印时,编译器会发出警告
var age : Int? = 18
print("My age is \(age)")//String interpolation produces a debug description for an optional value; did you mean to make this explicit?
- 以下三种方法可以消除警告
print("My age is \(age!)")//My age is 18
print("My age is \(String(describing: age))")//My age is Optional(18)
print("My age is \(age ?? 0)")//My age is 18
可选项的本质是枚举
let a: Optional = 1
如上代码所示,我们的点进源码的定义处,可以看到:
@frozen public enum Optional : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
}
所以,可选项的本质是枚举,它有两个case,分别是none和some,如果可选值为nil,它是none,如果有值,它是some。
可选嵌套
有如下代码
let a: Int? = 1
let b: Int?? = a
let c: Int??? = b
我们使用fr v -R
查看其结构
(lldb) fr v -R a
(Swift.Optional) a = some {
some = {
_value = 1
}
}
(lldb) fr v -R b
(Swift.Optional>) b = some {
some = some {
some = {
_value = 1
}
}
}
(lldb) fr v -R c
(Swift.Optional>) c = some {
some = some {
some = some {
some = {
_value = 1
}
}
}
}
可以看出,它是一层层的可选值封装,就像下面的二叉树结构:
从上图中我们可以看到,none可以出现在任意一层,那么在每一层的效果一样吗?
我们看如下代码:
let a: Int? = nil
let b: Int?? = a
let c: Int??? = b
let d: Int??? = nil
同样查看内存结构
(lldb) fr v -R a
(Swift.Optional) a = none {
some = {
_value = 0
}
}
(lldb) fr v -R b
(Swift.Optional>) b = some {
some = none {
some = {
_value = 0
}
}
}
(lldb) fr v -R c
(Swift.Optional>) c = some {
some = some {
some = none {
some = {
_value = 0
}
}
}
}
(lldb) fr v -R d
(Swift.Optional>) d = none {
some = some {
some = some {
some = {
_value = 0
}
}
}
}
我们看到,b和c都是some,而d是none,拿c来说,c是一个Optional.some(Optional.some(Optional.none)),而d因为是直接赋值为nil,所以它是一个Optional.none.
假如这个时候我们进行可选绑定
let a: Int? = nil
let b: Int?? = a
let c: Int??? = b
let d: Int??? = nil
if let _ = a{
print("a不为空")
}
if let _ = b{
print("b不为空")
}
if let _ = c{
print("c不为空")
}
if let _ = d{
print("a不为空")
}
//打印结果
b不为空
c不为空
我们再看这个例子
let a: Int? = nil
let b: Int?? = a
let c: Int??? = b
let d: Int??? = nil
if a == b{
print("a和b相等")
}
if b == c{
print("b和c相等")
}
if a == c{
print("a和c相等")
}
if c == d {
print("c和d相等")
}
if a == d{
print("a和d相等")
}
//打印结果
a和b相等
b和c相等
a和c相等
为什么结果是这样呢,我们查看关于可选值的==
的定义
extension Optional : Equatable where Wrapped : Equatable {
/// Returns a Boolean value indicating whether two optional instances are
/// equal.
///
/// Use this equal-to operator (`==`) to compare any two optional instances of
/// a type that conforms to the `Equatable` protocol. The comparison returns
/// `true` if both arguments are `nil` or if the two arguments wrap values
/// that are equal. Conversely, the comparison returns `false` if only one of
/// the arguments is `nil` or if the two arguments wrap values that are not
/// equal.
///
/// let group1 = [1, 2, 3, 4, 5]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first == group2.first {
/// print("The two groups start the same.")
/// }
/// // Prints "The two groups start the same."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In the following example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("23") // Optional(23)
/// if numberToFind == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// An instance that is expressed as a literal can also be used with this
/// operator. In the next example, an integer literal is compared with the
/// optional integer `numberFromString`. The literal `23` is inferred as an
/// `Int` instance and then wrapped as an optional before the comparison is
/// performed.
///
/// if 23 == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.
@inlinable public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool
}
再看一段代码
let a: Int? = 1
let b: Int?? = a
let c: Int??? = b
let d: Int??? = 1
if a == b{
print("a和b相等")
}
if b == c{
print("b和c相等")
}
if a == c{
print("a和c相等")
}
if c == d {
print("c和d相等")
}
if a == d{
print("a和d相等")
}
//打印结果
a和b相等
b和c相等
a和c相等
c和d相等
a和d相等
在==
的注释中有这样一段话:
The comparison returns
true
if both arguments arenil
or if the two arguments wrap values that are equal.
翻译过来就是,要返回true,要么是两个都是nil,要么是warp
值相等。
按照我们上面的案例,a=1,d=1
的时候,它们warp
值都是1,所以相等。a=nil,d=nil
的时候,a、b、c都是some,所以我们比较它里面的warp
值,都是nil,所以相等,而d是none,所以d和所有其他的都不相等。
我们可以测试一下:
let a: Int? = nil
let b: Int?? = a
let c: Int??? = b
let d: Int??? = Optional.none
if a == b{
print("a和b相等")
}
if b == c{
print("b和c相等")
}
if a == c{
print("a和c相等")
}
if c == d {
print("c和d相等")
}
if a == d{
print("a和d相等")
}
打印结果
a和b相等
b和c相等
a和c相等
c和d相等
a和d相等
结果也和我们的注释一样。
结构体
介绍
- Swift标准库中,绝大多数的公开类型都是结构体,枚举和类只占很小一部分
- Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
struct Date{
var year: Int = 2020
var month: Int
var day: Int
}
var date = Date(year: 2020, month: 1, day: 20)
date = Date(month: 2, day: 22)
- 所有的结构体都会有编译器自动生成的初始化器(initializer),这个初始化器叫做逐一初始化器,它保证每个成员都会被初始化,若之前已经初始化的,逐一初始化器中可以不传
自定义初始化器
- 一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其它初始化器了
struct Date{
var year: Int = 2020
var month: Int
var day: Int
init(year: Int,month: Int,day: Int) {
self.year = year
self.month = month
self.day = day
}
}
var date = Date(year: 2020, month: 1, day: 20)
date = Date(month: 2, day: 22)//报错 Missing argument for parameter 'year' in call
类
定义
- 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
class Date{
var year: Int = 2020
var month: Int
var day: Int
init(year: Int,month: Int,day: Int) {
self.year = year
self.month = month
self.day = day
}
}
类的初始化器
- 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
- 成员的初始化是在这个初始化器中完成的
class Point {
var x: Int = 10
var y: Int = 20
}
let p1 = Point()
class Point{
var x: Int
var y: Int
init() {
x = 10
y = 20
}
}
let p2 = Point()
//上面两段代码是完全等效的
结构体与类的本质区别
- 结构体是值类型(枚举也是值类型),类是引用类型(指针类型)
值类型
- 值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
- 类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
- 在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术,仅有写操作时,才会真正执行拷贝操作
引用类型
- 引用类型赋值给var、let或者给函数传参,是将内存地址拷贝一份,指向的是同一个文件,属于浅拷贝(shallow copy)
嵌套类型
struct Poker{
enum Suit : String {
case spades,hearts,diamonds,clubs
}
enum Rank : Int {
case tow = 2,three,four,five,six,seven,eight,nine,ten
case jack,queen,king,ace
}
}
print(Poker.Suit.hearts.rawValue)//hearts
var rank = Poker.Rank.five
rank = .king
print(rank.rawValue)//13
枚举、结构体、类都可以定义方法
- 一般把定义在枚举、结构体、类内部的函数,叫做方法
方法占用对象的内存吗?
不占用
方法的本质就是函数
方法、函数都存放在代码段
闭包
闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来作为参数传值。
相比OC的block,Swift的闭包有很多优化的地方:
- 可以根据上下文推断参数和返回值类型
- 从单行表达式闭包中隐式返回(也就是闭包体只有一行代码,可以省略return)
- 可以使用简化参数名,如$0,$1(从0开始,表示第i个参数)
- 提供了尾随闭包语法(Trailing closure syntax)
闭包表达式(Closure Expression)
- 在Swift中,可以通过func定义一个函数,也可以闭包表达式定义一个函数,因为函数就是一个特殊的闭包
func sum(_ v1: Int,_ v2: Int) -> Int { v1 + v2 }
var fn = { (v1: Int,v2: Int) -> Int in
v1 + v2
}
fn(10,20)//30
{
(参数列表) -> 返回值类型 in
函数体代码
}
闭包表达式的简写
func exec(v1: Int,v2: Int,fn: (Int,Int) -> Int){
print(fn(v1,v2))
}
exec(v1: 10, v2: 20,fn: { (v1, v2) -> Int in
v1 + v2
})
exec(v1: 10, v2: 20,fn : { v1, v2 in
v1 + v2
})
exec(v1: 10, v2: 20,fn : {$0 + $1})
exec(v1: 10, v2: 20,fn : +)
尾随闭包
- 如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
- 尾随闭包是一个被书写在函数调用括号外面(后面)的闭包表达式
func exec(v1: Int,v2: Int,fn: (Int,Int) -> Int){
print(fn(v1,v2))
}
exec(v1: 10, v2: 20) {
$0 + $1
}
- 如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要再函数名后面写圆括号
func exec(fn: (Int,Int) -> Int) {
print(fn(1,2))
}
exec(fn: { $0 + $1 })
exec(){ $0 + $1 }
exec{ $0 + $1 }
- 忽略参数
func exec(fn: (Int,Int) -> Int) {
print(fn(1,2))
}
exec{ _,_ in 10 }//10
- 尾随闭包示例:数组的排序
函数原型
@inlinable public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
/*
返回true:i1排在i2前面
返回false:i1排在i2后面
*/
func compare(i1: Int,i2: Int) -> Bool{
//大的排在前面
return i1 > i2
}
array.sort(by: compare)
print(array) //[9, 8, 6, 4, 3, 1]
array.sort { (i1, i2) -> Bool in
i1 < i2
}
print(array)//[1, 3, 4, 6, 8, 9]
//下方是逐步的简写
//因为已知返回为Bool,可以省略
array.sort { (i1, i2) in i1 < i2 }
//已知参数类型,用$0,$1分别指代第一个第二个参数
array.sort { $0 < $1 }
//其它已知,直接给一个判断条件
array.sort(by: <)
闭包的定义
一个函数和它所捕获的变量/常量环境组合起来,成为闭包
- 一般指定义在函数内部的函数
- 一般它捕获的是外层函数的局部变量/常量
- 闭包的内存在堆空间
- 闭包是引用类型(指针类型)
注意
如果返回值是函数类型,那么参数的修饰要保持统一
func add(_ num: Int) -> (inout Int) -> Void{
func plus(v: inout Int){
v += num
}
return plus
}
捕获值
闭包可以在其定义的上下文中捕获常量或变量
即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内应用和修改这些值
//案例一:
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementor() -> Int {
runningTotal += amount
return runningTotal
}
return incrementor
}
let incrementByTen = makeIncrementor(forIncrement: 10)
// 返回的值为10
print(incrementByTen())
// 返回的值为20
print(incrementByTen())
// 返回的值为30
print(incrementByTen())
//案例二:
var num = 10
let ss = { () -> Int in
num += 10
return num
}
print(ss())//20
print(num)//20
print(ss())//30
print(num)//30
num = 100
print(ss())//110
print(num)//110
print(ss())//120
print(num)//120
逃逸闭包(@escaping)
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。
举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
//因为回调在函数返回后才返回,所以必须加上@escaping标记
func download(_ completionHandler : @escaping ()->Void){
DispatchQueue.global().async {
Thread.sleep(forTimeInterval: 2)
DispatchQueue.main.async {
completionHandler()
}
}
}
自动闭包(@autoclosure)
自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
我们经常会调用采用自动闭包的函数,但是很少去实现这样的函数。举个例子来说,assert(condition:message:file:line:) 函数接受自动闭包作为它的 condition 参数和 message 参数;它的 condition 参数仅会在 debug 模式下被求值,它的 message 参数仅当 condition 参数为 false 时被计算求值。
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。
func returnSelfOrOther(_ num: Int,_ other: @autoclosure () -> Int) -> Int{
num >= 0 ? num : other()
}
let result = returnSelfOrOther(-1) { 20 }
print(result) // 20
- 为了避免与期望冲突,使用了@autoclosure的地方最好明确注释清楚:这个值会被推迟执行
- @autoclosure会自动将20封装成闭包{20}
- @autoclosure只支持()->T格式的参数
- @autoclosure并非只支持最后一个参数
- 空合运算符??使用了@autoclosure技术
- 有@autoclosure,无@autoclosure,构成了函数重载
注意
过度使用 autoclosures 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的