有Kotlin基础情况下学习Swift(详细篇)

310 阅读38分钟

通用

可结合👉🏻右边的目录查看更清晰明了

值类型和引用类型的区别

结构体是值类型,类是引用类型

值类型验证

import UIKit

struct Point {
    var x: Double
    var y: Double
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let point1 = Point(x: 3, y: 5)
        var point2 = point1
        
        print(point1)           // Point(x: 3.0, y: 5.0)
        print(point2)           // Point(x: 3.0, y: 5.0)
        
        point2.x = 5
        
        print(point1)           // Point(x: 3.0, y: 5.0)
        print(point2)           // Point(x: 3.0, y: 5.0)
        
    }
}

//打印point1 不随着 point2 而变化 。说明他们内存独立
Point(x: 3.0, y: 5.0)
Point(x: 3.0, y: 5.0)
Point(x: 5.0, y: 5.0)

引用类型验证

import UIKit

class Point {
   var x: Double = 0.0
   var y: Double = 0.0
}

class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()
       
       let point1 = Point()
       point1.x = 3.0
       point1.y = 5.0
       let point2 = point1
       
       print(point1)
       print(point2)
       
       point2.x = 5
       
       print(point1.x , point1.y)
       print(point2.x , point2.y)
   }
}

//打印point1 随着 point2 而变化 。说明他们公用一块内存
5.0 5.0
5.0 5.0

Optional(可选项)的理解

Swift中引入了可选项(Optional)的概念是为了解决在代码中关于某些变量或常量可能为nil的情况进行处理,然后减少了程序中的不确认性,使得程序更加稳定和安全

什么是Optional

在Swift中,可选项的类型是运用?来表明的,例如String?即为一个可选的字符串类型,表明这个变量或常量可能为nil。而关于不可选项,则直接运用相应类型的称号,例如String表明一个非可选的字符串类型。

var str: String = nil
var str1: String? = nil 

Optional实现原理

Optional实际上是Swift语言中的一种枚举类型。在Swift中声明Optional类型时,编译器会主动将其转换成对应的枚举类型,例如:

var optionalValue: Int? = 10
// 等价于:
enum Optional<Int> {
  case none
  case some(Int)
}
var optionalValue: Optional<Int> = .some(10)

在上面的代码中,咱们声明了一个Optional类型的变量optionalValue,并将其初始化为10。实际上,编译器会主动将其转换为对应的枚举类型,即Optional枚举类型的.some(Int),其间的Int便是咱们所声明的可选类型的相关值。

当咱们在运用Optional类型的变量时,能够经过判别其枚举值是.none仍是.some来确认它是否为nil。假如是.none,表明该Optional值为空;假如是.some,就能够经过拜访其相关值获取详细的数值。

Optional的源码实现为:

@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
	case none
	case some(Wrapped)															 
}
  • Optioanl其实是规范库里的一个enum类型

  • 用规范库实现语言特性的典型

  • Optional.none 便是nil

  • Optional.some 便是包装了实际的值

  • 泛型特点 unsafelyUnwrapped

  • 理论上咱们能够直接调用unsafelyUnwrapped获取可选项的值

可选项的解包

可选项是不能直接使用的,需要解包后才能使用,基本上有一下解包方式

!强制解包,例如:

let count = ddb!.count

在强制解包前,你如果不知道它是否为nil,那你需要先对它进行非nil的判断保护,否则强制解包一旦失败,程序会报错,如下代码:

if ddb != nil {
    let count = ddb!.count
    print(count)
}

这样即使我们使用了强制解包,但它的运行依然是安全的

guard 和if语句的区别

guard是 swift 2.0推出的新的判断语句的用法。guard语句和if语句类似,都是根据关键字之后的表达式的布尔值决定下一步执行什么。和if语句不同的是,guard语句只有一个代码块,而if语句可有多个代码块。(如 if 、else if、 else)
那么guard到底是什么作用呢?顾名思义,guard是作为保卫作用而存在的。当你不满足我的要求,那么请您出去;如果满足,则执行下一步操作。

与if语句相同的是,guard也是基于一个表达式的布尔值去判断一段代码是否该被执行。 与if语句不同的是,guard只有在条件不满足的时候才会执行这段代码。你可以把guard近似的看做是Assert,但是你可以优雅的退出而非崩溃。

guard 的用法

func fooNonOptionalGood(x: Int) {
    guard x > 0 else {
        // 变量不符合条件判断时,执行下面代码
        return
    }
    
    // 使用x
}

常用使用场景为, 用户登录的时候, 验证用户是否有输入用户名密码等

guard let userName = self.userNameTextField.text,
  let password = self.passwordTextField.text else {
    return
}

Assert 断言

使用断言的场合: 整数下标索引传递到自定义下标实现,但下标索引值可能太低或太高。 一个值被传递给一个函数,但是一个无效的值意味着该函数不能完成它的任务。 当前是一个可选值nil,但非nil值对于后续代码成功执行至关重要。 1.可能越界的时候 2.可能为nil,nil的时候影响较大 3.从其他地方传递过来的值,可能不对对下边影响比较大。

inout和 mutating

inout可以修改属性的值,inout是放在参数类型前,冒号后,mutating 放在方法的前面修改属性值

如果直接修改属性的值,会报错,

func testSwap(a: Int, b: Int) {
    let temp = a
    a = b
    b = temp
}

上面的代码会报编译错误

inout使用

func swap(ps: inout PointStruct) -> PointStruct {
    withUnsafePointer(to: &ps) { print("地址1: ($0)") }

    let temp = ps.x
    ps.x = ps.y
    ps.y = temp

    return ps
}

var ps1 = PointStruct(x: 10, y: 20)
print(ps1)
withUnsafePointer(to: &ps1) { print("地址2: ($0)") }
print(swap(ps: &ps1))
print(ps1)
withUnsafePointer(to: &ps1) { print("地址3: ($0)") }

mutating使用

struct Point {
    var x = 0
    var y = 0

    mutating func movePoint(x: Int, y: Int) {
        self.x += x
    }
}

更详细的说明

写时复制

在 Swift 中,所有的基本类型,包括整数、浮点数、字符串、数组和字典等都是值类型,并且都以结构体的形式实现。那么,我们在写代码时,这些值类型每次赋值传递都是会重新在内存里拷贝一份吗? 答案是否定的,想象一下,假如有个包含上千个元素的数组,然后你把它 copy 一份给另一个变量,那么 Swift 就要拷贝所有的元素,即使这两个变量的数组内容完全一样,这对它性能来说是多么糟糕。 而这个优化方式就是 Copy-On-Write(写时复制),即只有当这个值需要改变时才进行复制行为。

写时复制定义: 如果结构体中的引用在被改变的一瞬间时是唯一的话 (比如,没有声明另外一个变量引用的话),那么也不会有复制发生,内存的改变将在原地进行。如果不是,就需要先进行复制,然后对复制的值进行变化,而保持其他的持有者不受影响。

    var y = x
    x.append(5) //1,2,4,5
    y.removeLast() //1,2

这时,把x赋值给y时会发生复制。这时候两个数组的引用指向的是内存中的同一个位置。共享存储部分。 当改变x时这个共享会被检查到。 内存将会被复制出来。 我们就独立的改变了两个变量。 耗性能的元素复制操作只会在必要的时候发送。这个就叫做写时复制。 简单来说: 复制时用的是一个内存地址,当某一个集合改变时恰到好处的复制了一份出来

写时复制优点: 允许复制数组和原数组共享同一个内存地址,直到其中之一发生改变。这样的设计使得值类型可以被多次复制而无需耗费多余的内存,只有在变化的时候才会增加开销。因此内存的使用更加高效。

写时复制注意点: 复制结构体变量。里面进行的浅复制。对象本身不会被复制。只有引用会被复制。 “写时复制” 这个特性在 Swift 中是属于 值类型 的,而不是 Struct 独有的特性,在 Swift 中,典型的有 struct,enum,以及 tuple 都是值类型。而平时使用的 Int, Double,Float,String,Array,Dictionary,Set 其实都是用结构体实现的,也是值类型。

Swift 字符串操作

追加字符串append,

获取下标startIndex startIndex,

替换指定范围的字符串 replaceSubrange ,

删除指定范围removeSubrange,

检查前后缀 hasPrefix hasSuffix

Swift 增强字符串

// 多行字符串 界定符 转义符
// Swift 多行字符串,同 kotlin 的原始字符串,不需要手动添加换行符。可用于排版
var text1 = """
start
\(1)
2
end
"""
print(text1)


// 转义符
var text2 = "对单引号进行转义\'"
print(text2) // 对单引号进行转义'

// 使用界定符代替转义符
var text3 = #"对单引号进行转义'"#
print(text3) // 对单引号进行转义'

// 使用界定符时,转义符失去作用
var text4 = #"换行符1 \n 换行符2"#
print(text4) // 换行符1 \n 换行符2

// 使用界定符时,使用 \# 保留转义符的作用
var text5 = #"换行符1 \#n 换行符2"#
print(text5) // 会换行打印: 换行符1 换行符2

集合

数组

Swift提供了三种主要的集合类型,即数组、集合和字典,用于存储值的集合。数组是值的有序集合。集合是唯一值的无序集合。字典是键值关联的无序集合。

数组是值的有序集合,对于其中每个元素,我们可以使用下标对其直接进行访问(这又被称作随机访问 Swift 数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。

Swift 数组会强制检测元素的类型,如果类型不同则会报错,Swift 数组应该遵循像Array这样的形式,其中Element是这个数组中唯一允许存在的数据类型。

如果创建一个数组,并赋值给一个变量,则创建的集合就是可以修改的。这意味着在创建数组后,可以通过添加、删除、修改的方式改变数组里的项目。如果将一个数组赋值给常量,数组就不可更改,并且数组的大小和内容都不可以修改。

了解更多

  • 快捷创建重复元素的数组
var array4 = Array(repeating: "swift", count: 3) // ["swift", "swift", "swift"]
var array5 = Array(repeating: 1001, count: 3) // [1001, 1001, 1001]
  • 数组相加
var array6 = [1, 2, 3] + [4, 5, 6] // [1, 2, 3, 4, 5, 6]
  • 二维数组
var visited = Array(repeating: Array(repeating: false, count: matrix[0].count), count: matrix.count)
  • 数组遍历
let arr = [11, 22, 33]
for item in arr {
    print(item)
}
// 打印数组的下标及对应元素
for item in arr.enumerated() {
    print(item) // (offset: 0, element: 11) (offset: 1, element: 22) (offset: 2, element: 33)
}

// 下标遍历
for index in arr.indices {
    print(arr[index])
}

  • 数组操作
// 当数组声明为可变时,才能使用增,删,改等方法,常量数组不能进行修改相关操作
var array = [1, 2, 3, 4, 5, 6, 7, 8]
print(array.count) // 8

// 判断数组是空数组
if array.isEmpty {
    print("array is empty")
} else {
    print("array is not empty")
}

// 通过下标访问元素
var ele = array[1] // 2

// 截取新数组
var subArray = array[1...2] // [2, 3]

// 获取第一个元素
var firstEle = array.first // 1

// 获取最后一个元素
var lastEle = array.last // 8

// 修改下标对应的元素
array[1] = 22
array // [1, 22, 3, 4, 5, 6, 7, 8]

// 修改指定范围的元素
array[0...2] = [1, 2, 3] // [1, 2, 3]
 
// 追加单个元素
array.append(9) // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 追加一组元素
array.append(contentsOf: [10, 11, 12]) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

// 在指定位置插入单个元素
array.insert(0, at: 0) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

// 在指定位置插入一组元素
array.insert(contentsOf: [-3, -2, -1], at: 0) // [-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

// 移除指定元素
array.remove(at: 1) // -2

// 移除一组元素
array.removeSubrange(0...2) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

// 移除首个元素
array.removeFirst() // 1

// 移除末尾元素
array.removeLast() // 12

// 移除前几个元素
array.removeFirst(3) // [5, 6, 7, 8, 9, 10, 11]

// 移除后几个元素
array.removeLast(3) // [5, 6, 7, 8]

// 替换指定范围的元素
array.replaceSubrange(0...3, with: [1, 2, 3, 4]) // [1, 2, 3, 4]

// 判断包含指定元素
if array.contains(3) {
    print("array contains 3")
}

// 移除所有元素
array.removeAll() // []

var sortArr = [2, 1, 3, -1]

// 从小到大排序
sortArr.sorted(by: <) // [-1, 1, 2, 3]

// 从大到小排序
sortArr.sorted(by: >) // [3, 2, 1, -1]

// 获取数组最大值
sortArr.min() // -1

// 获取数组最小值
sortArr.max() // 3

  • 数组操作符

→ map和flatMap —— 如何对元素进行变换
→filter——元素是否应该被包含在结果中
→ reduce——如何将元素合并到一个总和的值中
→ sequence——序列中下一个元素应该是什么?
→ forEach——对于一个元素,应该执行怎么样的操作
→ sort,lexicographicCompare 和 partition —— 俩个元素应该以怎么样的顺序进行排列
→ index,first 和 contains ——元素是否符合某个条件
→ min 和 max——两个元素中的最小/最大值是哪个
→ elementsEqual 和 starts——俩个元素是否相等
→ split——这个元素是否是一个分割符 → accumulate——累加,和reduce 类似,不过是将所有元素合并到一个数组中,而且保留合并时每一步的值。
→ all (matching:) none(matching:) ——测试序列中是不是所有元素都满足某个标准,以及是不是没有任何元素满足某个标准。它们可以通过contains和它进行了精心对应的否定形式来构建。
→ count(where:) —— 计算满足条件的元素的个数,和filter相似,但是不会构建数组。
→ indices(where:)——返回一个包含满足某个标准的所有元素的索引的列表,和index(where:)类似,但是不会再遇到首个元素时就停止。
index(where:)
→ prefix(while:)——当判断为真的时候,将元素滤出道结果中。一旦不为真,就将剩余的抛弃。和filter类似,但是会提前退出。这个函数在处理无序列或者延迟计算(lazily-computed) 的序列时会非常有用。
→ drop(while:)—— 当判断为真的时候,丢弃元素。一旦不为真,返回将其余的元素。和prefix(while:) 类似,不过返回相反的集合

更多详情

集合Set

集合是唯一值的无序集合,用Set类型定义,

  1. 创建Set集合
// 创建Set
var set: Set<Int> = [1, 2, 3]
var set2 = Set(arrayLiteral: 1, 2, 3)
  1. 获取元素
// set 获取最小值
set.min()
// 获取第一个元素,顺序不定
set[set.startIndex]
set.first
// 通过下标获取元素,只能向后移动,不能向前
// 获取第二个元素
set[set.index(after: set.startIndex)]

// 获取某个下标后几个元素
set[set.index(set.startIndex, offsetBy: 2)]
  1. 常用方法
// 获取元素个数
set.count
// 判断空集合
if set.isEmpty {
   print("set is empty")
}
// 判断集合是否包含某个元素
if (set.contains(3)) {
    print("set contains 3")
}
// 插入
set.insert(0)
// 移除
set.remove(2)
set.removeFirst()

// 移除指定位置的元素,需要用 ! 拆包,拿到的是 Optional 类型,如果移除不存在的元素,EXC_BAD_INSTRUCTION
set.remove(at: set.firstIndex(of: 1)!)
set.removeAll()

var setStr1: Set<String> = ["1", "2", "3", "4"]
var setStr2: Set<String> = ["1", "2", "5", "6"]

// Set 取交集
setStr1.intersection(setStr2) // {"2", "1"}

// Set 取交集的补集
setStr1.symmetricDifference(setStr2) // {"4", "5", "3", "6"}

// Set 取并集
setStr1.union(setStr2) // {"2", "3", "1", "4", "6", "5"}

// Set 取相对补集(差集),A.subtract(B),即取元素属于 A,但不属于 B 的元素集合
setStr1.subtract(setStr2) // {"3", "4"}
var eqSet1: Set<Int> = [1, 2, 3]
var eqSet2: Set<Int> = [3, 1, 2]

// 判断 Set 集合相等
if eqSet1 == eqSet2 {
    print("集合中所有元素相等时,两个集合才相等,与元素的顺序无关")
}

let set3: Set = [0, 1]
let set4: Set = [0, 1, 2]

// 判断子集
set3.isSubset(of: set4) // set3 是 set4 的子集,true
set3.isStrictSubset(of: set4) // set3 是 set4 的真子集,true

// 判断超集
set4.isSuperset(of: set3) // set4 是 set3 的超集,true
set4.isStrictSuperset(of: set3) // set4 是 set3 的真超集,true
  1. Set 遍历
// 遍历元素
for ele in set4 {
    print(ele)
}

// 遍历集合的枚举
for ele in set4.enumerated() {
    print(ele)
}

// 下标遍历
for index in set4.indices {
    print(set4[index])
}

// 从小到大排序后再遍历
for ele in set4.sorted(by: <) {
    print(ele)
}
                              

字典

Swift字典的类型完整地写为Dictionary<Key, Value>,其中Key是可以用作字典键的值的类型,Value是字典为这些键存储的值的类型。

与数组中的元素不同,字典中的元素没有指定的顺序。当需要根据值的标识符来查找值时,可以使用字典,这与使用真实世界的字典来查找特定单词的定义的方式非常相似。类似于Java中的Map(HashMap)不过不太一样哦,字典中每个值(value),都关联唯一的健(key),健作为字典中值数据的标志符。

1.创建空字典

var emptyDict1: [Int : String] = [:]
var emptyDict2: Dictionary<Int, String> = Dictionary()
// 指定键值类型
var dict: [Int : String] = [0: "Zero", 1: "One", 2: "Two"]
var dict2: Dictionary<Int, String> = Dictionary(dictionaryLiteral: (1, "One"), (2, "Two"))
// 自动推断键值类型
var dictAuto = [0: "Zero", 1: "One", 2: "Two"]
var dic1=[1:1,2:12,3:32,4:16,5:15]
var dic2:Dictionary<String,String>=[:]
var dic3=Dictionary<String,String>()
var dic4=[String : String]()
  1. 获取元素
// 获取元素个数
dict.count // 3

// 判断字典为空
dict.isEmpty // false

// 获取键对应的值
dict[1] // One
  1. 更新键值对
// 修改键对应的值
dict[0] = "000" // 000

// 如果键存在,就更新,否则就新增键值对
dict[-1] = "-1" // -1

// 更新键对应的值,如果键不存在,返回 nil
if let lastValue = dict.updateValue("new one", forKey: 1) {
    print("the last value is (lastValue)") // the last value is One
}

// 移除键值对
dict.removeValue(forKey: -1) // -1

// 移除所有键值对
//dict.removeAll()
  1. 遍历字典

// dict [1: "new one", 2: "Two", 0: "000"]

// 遍历字典的键
for ele in dict.keys {
    print(ele)
}
// 遍历字典的值
for ele in dict.values {
    print(ele)
}

// 元组遍历,直接获取键值对
for (key, val) in dict {
    print("(key):(val)")
}

// 对 key 进行从小到大排序后遍历,并对值进行拆包
for ele in dict.keys.sorted(by: <) {
    print(dict[ele]!)
}
//1 读取字典元素
var test1Dic=["key1":"你好","key2":"Swift","key3":"正在学习","key4":"字典","key5":"取值",]
 
var test1Str=test1Dic["key2"]
println("(test1Str)")
 
//此处取字典中未定义的键 不会报错,取出来的值为nil
var test1Str2=test1Dic["key"]
println("(test1Str2)")
 
//2 获取字典元素的个数
println(test1Dic.count)
//3 增加字典的元素
test1Dic["key"]="test"
println(test1Dic)
 
//4 删除字典中的元素
 
test1Dic.removeValueForKey("key1")
println(test1Dic)
 
//5 修改字典中的元素
 
// 5.1 直接修改
test1Dic["key"]="testkey"
 
// 5.2 使用 updateValue
var oldStr=test1Dic.updateValue("testkeytest", forKey: "key")
println(oldStr)
println(test1Dic)
 
//6 遍历
//6.1遍历所有的键值对
 
for (key,value) in test1Dic{
    println("key:(key) value:(value)")
}
 
//6.2 遍历所有的键
for test6Str in test1Dic.keys{
    println(test6Str)
}
 
//6.2 遍历所有的值
for test6Str2 in test1Dic.values{
    println(test6Str2)
}
 
//7 字典转数组
//7.1 将所有的键转为数组
var test7Keys=Array(test1Dic.keys)
println(test7Keys)
 
//7.1 将所有的值转为数组
var test7Values=Array(test1Dic.values)
println(test7Values)
                                   

判断 流畅控制 循环

每个语言感觉都差不多,直接引用示例

  1. 运算符:三目,空合并,区间运算符
// 元组比较大小。需要元素个数一致,对应的位置的元素类型相同,每一个元素都必须支持比较运算操作。
// 从第一个元素开始比较,如果没有比较出结果,那么继续依次比较,直到比出结果为止。
var a = (1, 2, "3")
var b = (1, 2, "4")

var c = a < b // true

// 条件判断
if a > b {
    print("a > b")
} else {
    print("a < b")
}

// 三目运算,同 java
print(a > b ? "a > b" : "a < b") // a < b

var str: String? = "text"
var result: String = str != nil ? str! : ""

// 空合并运算符,如果 str 为 nil,则赋值空串,同 kotlin 的 Elvis 运算符 ?:
result = str ?? ""

// 区间运算符,[1, 5],范围是 >=1, <=5,类似于 Kotlin 的区间 1..5
var rang1 = 1...5

// 区间运算符,[1, 5),范围是 >=1, <5
var rang2 = 1..<5

// 判断是否在某个区间范围内
print(rang1 ~= 2) // true
  1. 循环:for-in, while, repeat-while
// 区间运算符用于循环
for index in rang1 {
    print(index)
}
// 中断循环 break
for index in 0...2 {
    if index == 1 {
        break
    }
    print(index) // 0
}
// 跳过当前循环 continue
for index in 0...2 {
    if index == 1 {
        continue
    }
    print(index) // 0 2
}

// 中断外层循环 break,类似于 Kotlin 的限定符
OutterLabel:for outterIndex in 0...2 {
    for index in 0...1 {
        if outterIndex == 1 {
            break OutterLabel
        }
        print(index) // 0 1
    }
}

// while 循环
var i = 0
while i < 3 {
    print(i) // 0, 1, 2
    i += 1
}

// 即 do-while,先执行一次循环,再判断条件
var k = 0
repeat {
    print(k) // 0, 1, 2
    k += 1
} while k < 3
  1. 流程控制:if-else, switch-case
// 流程控制 if-else
let number = 18
if number < 15 {
    print("number < 15")
} else if number >= 16 && number <= 20 {
    print("number >= 16 && number <= 20")
} else {
    print("number > 20")
}

// 流程控制 switch-case
var caseStr = "a"
switch caseStr {
case "a":
    print("value is a")
case "b":
    print("value is b")
default:
    print("default value")
}

// switch 子句多条件匹配
switch caseStr {
case "a","b","c":
    print("match success")
default:
    print("default value")
}

// switch 子句区间范围匹配
let age = 18
switch age {
case 16..<18:
    print("match age 16...18")
case 18...20:
    print("match age 18..<20")
default:
    print("default value")
}

// switch 元组匹配
let intTuple = (1, 2)
switch intTuple {
case (1, 2):
    print("match success 1, 2")
    fallthrough // 继续执行后续 case 匹配,不跳出 switch
case (2, 2):
    print("match success 2, 2")
    fallthrough
case (_, 2):
    // 选择性匹配,第一个匿名不关注,只有第二个能匹配,就算匹配成功
    print("match success _,2")
    fallthrough
case (0...2, 0...2):
    // 匹配元组元素的范围
    print("match range success")
case (let a, 1):
    print("捕获元素: (a)")
case let(a, b) where a < b:
    // 同 (let a, let b),增加 where 字句判断
    print("捕获元组: (a),(b)")
    fallthrough
default:
    print("default")
}

这里fallthrough 让 case 之后的语句会按顺序继续运行,且不论条件是否满足都会执行。

Swift 中的 switch 不会从上一个 case 分支落入到下一个 case 分支中。只要第一个匹配到的 case 分支完成了它需要执行的语句,整个switch代码块完成了它的执行。

注意: 在大多数语言中,switch 语句块中,case 要紧跟 break,否则 case 之后的语句会顺序运行,而在 Swift 语言中,默认是不会执行下去的,switch 也会终止。如果你想在 Swift 中让 case 之后的语句会按顺序继续运行,则需要使用 fallthrough 语句

  1. guard-else 守护判断
// 调用 method
method()
func method() {
    // 守护语句,当 guard 后面的条件成立时,才继续执行,替换之前的 if-return
    guard number > 20 else {
        return
    }
    print("continue execute")
}

函数

函数创建与调用

  1. 创建函数
// 创建函数,无参,无返回值,同 func func1() -> Void
func func1() {
    print("no params func")
}
func1()
// 创建函数,带参,带返回类型
func func2(param: Int) -> Bool {
    return param > 60
}

func2(param: 80)

// 创建函数,带多个参数,返回类型为元组
func func3(param1: String, param2: Int) -> (result1: String, result2: Int) {
    return (param1 + "11", param2 + 20)
}

let tuple = func3(param1: "param", param2: 60)
if tuple.result1.starts(with: "param") {
    print(tuple.result2)
}

// 创建函数,带参,返回类型为 Optional
func func4(param: Int) -> Int? {
    guard param > 2 else {
        return nil
    }
    return param + 2
}

if let result = func4(param: 3) {
    print(result)
}
  1. 函数内外参数命名
// 函数参数指定外部名称
func outerNameFunc(name1 param1: Int, name2 param2: Int, param3: Int) {
    print(param1, param2, param3)
}
// 函数参数使用外部名称
outerNameFunc(name1: 1, name2: 2, param3: 3)

func normalFunc(param1: Int, param2: Int, param3: Int) {
    print(param1, param2, param3)
}
// 默认函数参数的内部名称和外部名称一致,调用函数时需要指定参数名称
normalFunc(param1: 1, param2: 2, param3: 3)

// 调用函数时省略参数名称
func annoFunc(_ param1: Int, _ param2: Int, _ param3: Int) {
    print(param1, param2, param3)
}
annoFunc(1, 2, 3)
  1. 函数参数指定默认值
func func5(param1: Int, param2: Int = 2, param3: Int = 3) {
    print(param1, param2, param3)
}
// 调用时,可以只传入没有默认值的参数
func5(param1: 1)
// 调用时,参数位置要严格对应
func5(param1: 1, param2: 22)


// 函数参数指定默认值
func func6(param1: Int, param2: Int = 2, param3: Int) {
    print(param1, param2, param3)
}
// 调用时,参数位置要严格对应
func6(param1: 1, param3: 33)
  1. 可变参数
// 函数传入多个可变数量的参数,类似于 Kotlin 的 vararg
func mutableParamFunc(param: Int...) {
    var sum = 0
    for ele in param {
        sum += ele
    }
    print(sum)
}
mutableParamFunc(param: 1, 2)
mutableParamFunc(param: 1, 2, 3, 4)

// swift 的函数参数值(除引用类型外)默认是不可修改的
func immutableParam(param: Int) {
//    param += 1 // 编译失败 error: left side of mutating operator isn't mutable: 'param' is a 'let' constant
}

// 为了可以在函数参数内部修改参数值,可以使用 inout 修饰参数
func immutableParam(param:inout Int) {
    param += 1 // 编译通过
    print(param) // 2
}
// 调用时需要使用 & 符号
var number = 1
immutableParam(param: &number)
print(number) // 2, number的值也被修改了
  1. 函数类型引用,函数嵌套
// 函数可以作为类型进行声明,就像使用其他类型一样
let func7Name: (Int, Int) -> Bool
// 将闭包赋值给函数变量
func7Name = {(param1: Int, param2: Int) in
    return param1 > param2
}
// 调用函数变量
func7Name(1, 2) // false

// 函数作为参数传入
func func8(funParam: (Int, Int) -> Bool) {
    print(funParam(2, 1)) // true
}
// 将 func7 传入 func8
func8(funParam: func7Name)

// 函数作为返回值类型
func func9() -> (Int, Int) -> Bool {
    return func7Name // 将 func7Name 返回
}

// 函数嵌套
func outerFunc() {
    let outerScope = "outer scope"
    func innerFunc() {
        print(outerScope, "in inner func")
    }
    // 在外部函数内调用内部嵌套函数,在外部函数以外无法调用它
    innerFunc()
}
// 调用外部函数
outerFunc()

defer的用法

很简单,用一句话概括,就是 defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行。

这个关键字就跟 Java 里的 try-catch-finally 的finally一样,不管 try catch 走哪个分支,它都会在函数 return 之前执行。而且它比 Java 的finally还更强大的一点是,它可以独立于 try catch 存在,所以它也可以成为整理函数流程的一个小帮手。在函数 return 之前无论如何都要做的处理,可以放进这个 block 里,让代码看起来更干净一些~
了解更多
代码示例

func testDefer() {
    defer {
        print("方法中defer内容")
    }
    if true {
        defer {
            print("if 中defer内容")
        }
        print("if中最后的代码")
    }
    print("方法中的代码")
    if true {
        return
    }
    print("方法结束前最后一句代码")
}
testDefer()        

运行结果

if中最后的代码
ifdefer内容
方法中的代码
方法中defer内容        

闭包

闭包是一个捕获了外部变量或者常量的函数,可以有名字的函数,可以是匿名的函数,也可以是不捕获外部变量的函数。所以可以说闭包是特殊的函数。
闭包是自包含的函数代码块,可以在代码中被传递和使用。与 C 和 ObjC 中的代码块(blocks)比较相似。 了解更多

后置闭包、逃逸闭包与自动闭包

// 定义闭包,类似于 Kotlin 的 lambda 表达式。
// 闭包一般是为了处理回调,传递功能代码块
// 闭包标准语法结构:{(param list) -> valueType in block}
let closureFunc1 = {(param1: Int, param2: Int) -> Int in
    return param1 + param2
}

// 调用闭包
closureFunc1(1, 2) // 3

// 闭包可省略返回值
let closureFunc2 = {(param1: Int, param2: Int) in
    return param1 + param2
}
closureFunc2(1, 3) // 4

// 如果闭包只有一行代码,可以省略 return
let closureFunc3 = {(param1: Int, param2: Int) in
    return param1 < param2
}
closureFunc3(1, 2) // true

// 入参为闭包
func func10(closureParam: (Int, Int) -> Bool) {
   closureParam(2, 1)
}
// 使用默认参数名
func10(closureParam: { $0 > $1 }) // true

// 后置闭包,当最后一个参数为闭包时,简化写法:
func10() {
    $0 > $1
}

// 非逃逸闭包:函数的生命周期结束后,闭包也将被销毁
// 定义的闭包默认都是非逃逸的

// 逃逸闭包:函数的执行结束后,闭包在函数外仍可使用
// 定义逃逸闭包使用 @escaping ,一般用于异步回调
func func11(closureParam: @escaping (Int, Int) -> Bool) {
}
        
// 定义自动闭包使用 @autoclosure,对简单闭包的自动生成。
// 自动闭包默认非逃逸。自动闭包不能够有参数,单表达式
func autoCloseFunc(closureParam: @autoclosure () -> Int) {
    print(closureParam()) // 6
}
autoCloseFunc(closureParam: 1 + 2 + 3)
        
  • 闭包和函数是引用类型,将函数或闭包赋值给一个常量还是变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用。

    func makeInCount(count: Int) -> () -> Int {
        var total = 0
        func incrementer() -> Int {
            total += count
            return count
        }
        return incrementer
    }
    let incrementBySeven = makeInCount(count: 7)
    incrementBySeven()
    let alsoIncrementBySeven = incrementBySeven
    alsoIncrementBySeven()
    
  • 逃逸闭包,当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。 例如网络请求

    func request(result:@escaping((String)->())){
        DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 10) {
            result("数据结果")
        }
    }
    
  • 非逃逸闭包, 永远不会离开一个函数的局部作用域的闭包就是非逃逸闭包。

    func player(complete:(Bool)->()){
        complete(true)
    }
    
  • 自动闭包,自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。当闭包作为参数传入 可用@autoclosure标记闭包参数 ,可将参数当函数调用而并非以闭包的形式。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包

    
     var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
     print(customersInLine.count)
     // 打印出“5”
    
     let customerProvider = { customersInLine.remove(at: 0) }
     print(customersInLine.count)
     // 打印出“5”
    
     print("Now serving (customerProvider())!")
     // 打印出“Now serving Chris!”
    
     print(customersInLine.count)
     // 打印出“4”
     
     // customersInLine is ["Ewa", "Barry", "Daniella"]
     func serve(customer customerProvider: @autoclosure () -> String) {
         print("Now serving (customerProvider())!")
     }
     serve(customer: customersInLine.remove(at: 0)) 
     // 打印“Now serving Ewa!”
    
     //不用  @autoclosure 修饰 
     func serve(customer customerProvider: () -> String) {
         print("Now serving (customerProvider())!")
     }
     serve(customer: { customersInLine.remove(at: 0) } )
     //打印“Now serving Ewa!”
    

进制运算

  1. 位运算
// Swift 位运算
var sixteen: UInt8 = 0b00010000 // 二进制
print(sixteen) // 8

// Swift 按位与 & : 操作数相同的位进行逻辑与运算
// 即两个对应位的值都为1,结果为1,否则为0。示例:
var result1 = sixteen & 0b00001111 // 0
var result2 = 0b00000111 & 0b00000001 // 1

// Swift 按位或 | :操作数相同的位进行逻辑或运算
// 即两个对应位的值有一个为1,结果为1,否则为0。示例:
var result3 = 0b00000111 | 0b00000000 // 0b00000111 7
var result4 = 0b00000001 | 0b00000010 // 0

// Swift 按位取反 ~ :将操作数的每一位都取反,如果当前位是1,则取反为0
var result5 = ~sixteen // 0b11101111 255 - 16 = 239

// Swift 按位异或 ^
// 即两个对应位的值相同,结果为0,否则为1。示例:
var result6 = 0b00000111 ^ 0b00000000 // 0b00000111 7
var result7 = 0b00000111 ^ 0b00001111 // 0b00001000 8

// Swift 按位左移 << ,按位右移 >>
var result8 = 0b00000111 << 1 // 0b00001110 7 << 1 = 7 * 2 = 14
var result9 = 0b00000111 >> 1 // 0b00000011 7 >> 1 = 7 / 2 = 3

// Swift 按位左移 << ,按位右移 >> 对于位数较低类型,会出现数据丢失的情况
var result10: UInt8 = 0b00001000 << 5 // 0
var result11: UInt8 = 0b00010000 >> 5 // 0
  1. 溢出运算符
// 溢出运算符
var num: UInt8 = 255
var result12 = num &+ 1 // 溢出后变为0
result12 = result12 &- 1 // 溢出后再减1255
result12 = result12 &* 2 // 即 0b11111111 << 1 = 0b11111110 = 254
  1. 重载运算符

// 重载运算符 + , 元组相加,扩展加号的新功能
func +(param1: (Int, Int), param2: (Int, Int)) -> (Int, Int) {
    return (param1.0 + param2.0, param1.1 + param2.1)
}
var tuple1: (Int, Int) = (1, 2)
var tuple2: (Int, Int) = (1, 2)
let tuple = tuple1 + tuple2 // (2, 4)
  1. 自定义运算符
// 自定义运算符
// 自定义前缀运算符,即只需要一个操作数,运算符在操作数前面
prefix operator ++
prefix func ++(param: Int) -> Int {
    return param + 1
}
++1 // 2

// 自定义中缀运算符,即需要两个操作数,运算符在两个操作数中间
infix operator **
func **(param1: Int, param2: Int) -> Int {
    return param1 * param1 + param2 * param2
}
1 ** 2 // 5

// 自定义后缀运算符,即只需要一个操作数,运算符在操作数后面
postfix operator --
postfix func --(param: Int) -> Int {
    return param - 1
}
1-- // 0

枚举

枚举为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。 枚举成员可以指定任意类型的关联值存储到枚举成员中,就像其他语言中的联合体(unions)和变体(variants)。你可以在一个枚举中定义一组相关的枚举成员,每一个枚举成员都可以有适当类型的关联值

枚举支持IntDoubleString等基础类型,也有默认枚举值String类型默认枚举值为casekey名称,IntDouble数值型默认枚举值为0开始,+1递增。
代码:

// 写法一
// 不需要逗号隔开
enum Weak1 {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}

// 写法二
// 也可以直接一个case,然后使用逗号隔开
enum Weak2 {
    case MON, TUE, WED, THU, FRI, SAT, SUN
}

// 定义一个枚举变量
var w: Weak1 = .MON


/*
String类型的enum
- =左边的值是枚举值,例如 MON
- =右边的值在swift中称为 RawValue(原始值),例如 "MON"
- 两者的关系为:case 枚举值 = rawValue原始值
*/
enum Week: String{
    case MON = "MON"
    case TUE = "TUE"
    case WED = "WED"
    case THU = "THU"
    case FRI = "FRI"
    case SAT = "SAT"
    case SUN = "SUN"
}

结构体

声明结构体的关键字是struct,使用起来和类相似,但类的成员是引用类型,而结构体的成员是值类型。

值类型在传递和赋值的时候,是进行复制的,那也就是说在修改一处复制体的时候,原来被复制的对象是不受影响的。

引用类型在传递和赋值的时候,是传递引用对象的一个“指向”,所以当对该引用进行修改的时候,是直接直接修改到最原始的对象,即,一旦修改值,则对该原始对象的所有引用都会被同步修改。

在内存中,引用类型的变量是在堆上存储和操作的。值类型的变量是在栈上存储和操作的。两者相比起来,在堆上的变量操作会比较复杂和耗时。所以苹果官方推荐使用结构体,这样可以提高App的运行效率。

结构体的优势: 结构较小,适用于复制,相比一个class的实例被多次引用,struct结构体更加安全。 无须担心内存泄漏或者多线程冲突安全。

 struct student {
        var name = "name"
        var index = 202100
        var height = 160.00
        var profession = "profession"
        var sex = "男"
    }
    var xiaoMing = student()
    xiaoMing.name = "xiaoMing"
    xiaoMing.index = 202101
    xiaoMing.height = 172.5
    xiaoMing.profession = "计算机科学与技术"
    xiaoMing.sex = "男"
    print("\(xiaoMing)")

运行结果:

student(name: "xiaoMing", index: 202101, height: 172.5, profession: "计算机科学与技术", sex: "男")

当然也可以在定义结构体的时候不给出初始值,而是给出数据的基本类型。 因为swift中函数也是一种类型,所以结构体中也可以定义函数。

类是引用类型。也就意味着一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体 实例内存地址的引用。

// 定义一个类
class PhoneClass {
    
    // 定义价格属性
    var price: Int = 0
    
    // 定义品牌属性
    var brand: String = ""
    
    // 定义型号属性
    var model: String = ""
    
    // 定义降价促销方法
    func discount() {
        price -= 800
    }
    
    // 当三个属性都有默认值的时候,可以不写 init
    init(price: Int, brand: String, model: String) {
        self.price = price
        self.brand = brand
        self.model = model
    }
}
// 创建 class 实例
var huaweiPhone = PhoneClass(price: 5999, brand: "huawei", model: "p40 pro")

// 类属于引用类型,变量传递后,修改值会影响引用的变量
let huaweiNewPhone = huaweiPhone
huaweiPhone.price += 1000

// 值修改后会影响原变量的值,huaweiPhone.price: 6999, huaweiNewPhone.price: 6999
print("huaweiPhone.price: \(huaweiPhone.price), huaweiNewPhone.price: \(huaweiNewPhone.price)")
       

属性

存储属性

存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是变量存储属性(用关键字 var 定义),也可以是常量存储属性(用关键字 let 定义)。
了解更多

// Swift5 存储属性
class Phone {
    var system = "iOS"
    
    // 常量存储属性,一旦实例化,不能修改
    let brand: String
    
    // 存储属性,可以修改
    var price: Int
    
    // 当类被实例化时,要保证所有的属性都初始化完成,除非属性有默认值
    init(brand: String, price: Int) {
        self.brand = brand
        self.price = price
        print("brand: (brand), price: (price)")
    }
}

// 修改类的属性值
let iPhone = Phone(brand: "iPhone", price: 5999)
iPhone.price -= 600

// 延时存储属性:当类初始化时,延时存储属性不被初始化,当被调用时才初始化
class Consumer {
    var money: Int
    lazy var phone: Phone = Phone(brand: "iPhone", price: 6999)
    
    init(money: Int) {
        self.money = money
    }
}

// 只初始化了money
var richMan = Consumer(money: 100_000)
// 延时属性 phone 被初始化
print(richMan.phone) // brand: iPhone, price: 6999

计算属性

特征:仅有get(readOnly语义)或有get+set的属性是计算型属性,有get+set的属性仅作为其他属性的外部接口

  • 计算型属性本身不占用内存内控,所以不能赋值
    get+set为计算型属性的原因:
  • 真正赋值的过程是存在于set方法中并被底层包装掉的,如果我们手动实现了set方法,就无法进行正确的赋值

get+set正确的使用方法:作为其他属性的外部接口

// Swift5 计算属性
class Android {
    // 常量存储属性,一旦实例化,不能修改
    let system: String = "android"
    // 存储属性
    var version = "12"
    // api 级别
    var apiLevel: String = "31"
    
    // 计算属性,向外提供接口访问类实例的某种状态,这种状态和类实例的属性值相关联
    var info: String {
        get {
            return "system: (system), version: (version), level: (apiLevel)"
        }
        set {
            // 默认使用 newValue 指代新值
            version = newValue.split(separator: "-").first?.description ?? ""
            apiLevel = newValue.split(separator: "-").last?.description ?? ""
        }
    }
    
    // 计算属性 价格,简单模拟
    var price: ClosedRange<Int> {
        get {
            // 当版本高于30时,价格在[4000,6999]之间
            if (apiLevel > "30") {
                return 4000...6999
            } else {
                return 1000...3999
            }
        }
        // 自定义传值名称 newPrice
        set(newPrice) {
            // 当价格高于3999时,版本为31
            if (newPrice.lowerBound > 3999) {
                apiLevel = "31"
                version = "12"
            } else {
                apiLevel = "30"
                version = "11"
            }
        }
    }

}

var newPhone = Android()
print(newPhone.info) // system: android, version: 12, level: 31

newPhone.info = "11-30"
print(newPhone.info) // system: android, version: 11, level: 30

newPhone.price = 4000...4999
print(newPhone.info) // system: android, version: 12, level: 31

属性监听器

  • Swift中可以通过属性观察者来监听和响应属性值的变化

  • 通常是监听存储属性和类属性的改变.(对于计算属性,我们不需要定义属性观察者,因为我们可以在计算属性的setter中直接观察并响应这种值的变化)

  • 我们通过设置以下观察方法来定义观察者

    • willSet:在属性值被存储之前设置。此时新属性值作为一个常量参数被传入。该参数名默认为newValue,我们可以自己定义该参数名
    • didSet:在新属性值被存储后立即调用。与willSet相同,此时传入的是属性的旧值,默认参数名为oldValue
    • willSet与didSet只有在属性第一次被设置时才会调用,在初始化时,不会去调用这些监听方法

监听的方式如下: 监听age和name的变化

import UIKit

class Person: NSObject {
    // 属性监听器
    var name : String? {
        // 属性即将改变时进行监听
        willSet {
            print(name)
            print(newValue)
        }
        
        // 属性已经改变时进行监听
        didSet {
            print(name)
            print(oldValue)
        }
    }
}


let p = Person()
p.name = "why"
p.name = "yz"        

示例2

class iOS {
     
    var brand: String {
        // 此属性将要被赋值时会调用,默认带一个 newValue 字段。
        // 注意:初始化时不会被调用,从第二次赋值时才开始被调用
        willSet {
            print("new value : (newValue)")
        }
        // 此属性已经被赋值后会调用,默认带一个 oldValue 字段
        didSet {
            print("old value : (oldValue)")
        }
    }
    
    var price: Int {
        // 自定义传值名称
        willSet(newPrice) {
            print("new price : (newPrice)")
        }
        // 自定义传值名称
        didSet(oldPrice) {
            print("old price : (oldPrice)")
        }
    }
        
    init(brand: String, price: Int) {
        self.brand = brand
        self.price = price
        print("brand: (brand), price: (price)")
    }
}

let newIPhone = iOS(brand: "iphone 12", price: 5999)
newIPhone.brand = "iphone 13"
newIPhone.price = 6999

属性包装器

image.png 了解更多1 了解更多2

// Swift5 属性包装器
@propertyWrapper
struct StringNotEmpty {
    var value: String
    
    init() {
        self.value = "default string"
    }
    
    var wrappedValue: String {
        get { return value }
        set {
            if (newValue.count > 0) {
                self.value = newValue
            } else {
                self.value = "default string"
            }
        }
    }
}

class Student: CustomStringConvertible {
    @StringNotEmpty
    var name: String
    
    var description: String {
        return "student's name is (name)"
    }
}

let student = Student()
student.name = ""
print(student) // student's name is default string

静态属性

在介绍静态属性之前,我们先来看一个类的设计,有一个Account(银行账户)类,假设它有3个属性:amount(账户金额)、interestRate(利率)和owner(账户名)。在这3个属性中,amount和owner会因人而异,不同的账户这些内容是不同的,而所有账户的interestRate都是相同的。 amount和owner属性与账户个体有关,称为实例属性。interestRate属性与个体无关,或者说是所有账户个体共享的,这种属性称为静态属性或类型属性。 3种面向对象类型(结构体、枚举和类)都可以定义静态属性, 了解更多

// Swift5 静态属性
class BaseClass {
    // 静态存储属性
    static var param = "param"
    
    // 静态计算属性
    static var computeParam: String {
        return "computeParam"
    }
    
    // 可被继承的静态计算属性
    class var openParam: String {
        return "openParam"
    }
    
    // 声明静态方法,即类方法
    static func method() {
        print("static method")
    }
    
    // 声明可被继承的静态方法
    class func openMethod() {
        print("static openMethod")
    }
}

class SubClass : BaseClass {
    // 重写父类的 openParam 属性,类似于 Kotlin 的 open 修饰
    override class var openParam: String {
        return "SubClass openParam"
    }
    
    override class func openMethod() {
        print("SubClass openMethod")
    }
}

// 调用静态属性
BaseClass.param
BaseClass.computeParam
BaseClass.openParam
SubClass.openParam

// 调用静态方法
BaseClass.method()
BaseClass.openMethod()
SubClass.openMethod()

方法(函数)/构造方法

  • 所有的函数前面都用func来修饰
  • 函数名()的()里写的是参数,这里是虚参,也就是参数名称
  • 函数的参数默认是常量,而且只能是常量
  • 返回值是写在参数的后面,并且使用->隔开,sum函数返回的是Int类型
  • 在调用时也需要加上参数名称,这样传值会更准确
  • 在sum函数中我写了文档注释,这样便于后续维护,而且在调用函数时可以更加明确函数的职责
  • 因为是单一的表达式,所以无需写return,编译期会自动判断
    了解更多1了解更多2
  1. Swift 类的构造方法
// Swift5 类的构造方法
// 1. 在构造方法中需要给没有默认值的属性初始化值
class Demo1 {
    // 含有默认值
    var param1: String = "default"
    
    // 未指定默认值,需要在 init() 中初始化
    var param2: String
    
    // 未指定默认值,类型为 Optional,可空,
    // 默认值为 nil, 不需要在 init() 中初始化
    var param3: String?
    
    init(param: String) {
        self.param2 = param
    }
}

// 2. 如果属性有默认值,则自动生成一个无参构造方法 init(),即初始化后的实例的属性都有默认值
class Demo2 {
    var param1 = "param1 String"
    var param2 = "param2 String"
}

var demo2 = Demo2()
print("param1: (demo2.param1), param2: (demo2.param2)")
  1. Swift 结构体的构造方法
// Swift5 结构体的构造方法
struct StructDemo {
    var param1: String
    var param2: String
    
    // 结构体的构造方法会默认生成,将所有属性作为参数
    init(param1: String, param2: String) {
        self.param1 = param1
        self.param2 = param2
    }
    
    init() {
        // 在自定义的构造方法中调用默认的构造方法
        self.init(param1: "init value1", param2: "init value2")
    }
}

// 调用结构体默认的构造方法
var structIns1 = StructDemo(param1: "value1", param2: "value2")

// 调用结构体自定义的构造方法
var structIns2 = StructDemo()
  1. Swift 指定构造 方法 和 便利构造 方法 指定构造方法 designated 与 便利构造方法 convenience 。使用原则:
  • 子类的指定构造方法中必须调用父类的指定构造方法
  • 便利构造方法中必须调用当前类的其他构造方法
  • 便利构造方法最终是要调用指定某个构造方法
// Swift5 指定构造方法和便利构造方法
// 定义一个含有指定构造和便利构造的父类
class Base {
    
    // 默认的指定构造方法
    init() {
        print("base class designated constructor method")
    }
    
    // 声明一个便利构造方法
    convenience init(param: Int) {
        print("base class convenience constructor method")
        // 最终调用指定构造方法
        self.init()
    }
}

var baseIns = Base(param: 0)

// 继承父类
class Sub : Base {
    
    override init() {
        // 如果重写父类的指定构造方法,必须调用 super
        super.init()
    }
    
    convenience init(param: Int) {
        // 在便利构造方法中调用指定的构造方法
        print(param)
        self.init()
    }
    
    convenience init(param1: Int, param2: Int) {
        // 在便利构造方法中调用另一个便利构造方法
        print(param1, param2)
        self.init(param: param1)
    }
}

var subIns = Sub(param1: 0, param2: 1)
  1. Swift 构造方法的安全性检查
  • 必须在调研父类的指定构造方法前完成自身属性的赋值
  • 必须在调用父类指定的构造方法之后,在子类中才能修改父类的属性值
  • 在调用父类的构造方法之后,才能使用 self 关键字
  • 在便利构造方法中要修改属性值必须在调用指定构造方法之后
// Swift5 构造方法的安全性检查
class BaseCheck {
    var field: String
    init(field: String) {
        self.field = field
    }
}

class SubCheck: BaseCheck {
    var subField: String
    
    init() {
        // 1. 必须在调研父类的指定构造方法前完成自身属性的赋值
        subField = "sub field value"
        super.init(field: "base field value")
        
        // 2. 必须在调用父类指定的构造方法之后,在子类中才能修改父类的属性值
        field = "base field set in sub"
        
        // 3. 在调用父类的构造方法之后,才能使用 self 关键字
        self.subField = "sub field value set again"
        
        print(subField, field)
    }
    
    convenience init(param: Int) {
        // 4. 在便利构造方法中要修改属性值必须在调用指定构造方法之后
        self.init()
        subField = "subField set in convenience (param)"
        field = "field set in conveniencem (param)"
        
        print(subField, field)
    }
}

var checkIns = SubCheck(param: 1)
  1. Swift 定义可失败的构造方法
// Swift5 定义可失败的构造方法
class CanBeNil {
    
    var field: Int
    
    // 构造可能会失败返回 nil
    init?(param: Int) {
        guard param > 0 else {
            return nil
        }
        field = param
    }
}

let ins = CanBeNil(param: 0) // nil
  1. Swift 必要构造方法 与 析构方法

// Swift5 必要构造方法与析构方法
class DemoClass {
    
    var field: Int
    
    // 声明必要构造方法,子类必须继承或重写
    required init(param: Int) {
        field = param
    }
    
    // 析构方法,类实例被销毁
    deinit {
        // 实例被释放
        print("demo instance destroy")
    }
}
// 定义可选类型
var demoIns: DemoClass? = DemoClass(param: 1)
demoIns = nil // 被赋值 nil 时,deinit会调用

内存管理/引用

Swift 是一种快速、安全、现代的编程语言,它具有自动引用计数(ARC)机制来管理内存。这个机制可以自动追踪和释放不再需要的内存,使得开发者不必手动处理内存分配和释放的问题

Swift 内存销毁时机

// Swift5 内存销毁时机
// 引用类型的内存销毁时机
class ClassDemo {
    var a = "value a"
    deinit {
        // 实例被释放
        print("deinit class a")
    }
}

// 可空类型
var ins1: ClassDemo? = ClassDemo()
var ins2 = ins1
var ins3 = ins2

ins1 = nil // 取消 ins1 引用
ins2 = nil // 取消 ins2 引用
print(String(describing: ins3?.a)) // 此处 ins3 引用的实例依然在,Optional("value a")

// 对实例引用被全部取消,ClassA 实例此处才销毁
ins3 = nil // deinit class a

Swift 单向引用


// Swift5 单向引用
class ClassA {
    
    deinit {
        print("deinit ClassA")
    }
    
    func foo() {
        print("func foo in ClassA")
    }
}

class ClassB {
    // 此处引用 ClassA 的实例
    var ins: ClassA?
    
    init(ins: ClassA?) {
        self.ins = ins
    }
    
    deinit {
        print("deinit ClassB")
    }
}

var clzA: ClassA? = ClassA()
var clzB: ClassB? = ClassB(ins: clzA)

// 此处 clzA 所引用的内存并未释放
clzA = nil
// 依然可以调用 clzB 中的 clzA 实例的 foo 方法
clzB?.ins?.foo() // func foo in ClassA
// 此时 ClassB 实例被释放,不再有引用指向 ClassA 随即所占内存也被释放
clzB = nil // deinit ClassB \n deinit ClassA

Swift 循环引用

内存泄漏是一种常见的问题,指的是在程序运行时,一些对象无法被回收并释放,从而导致内存占用不断增加,最终导致程序崩溃。在 Swift 中,内存泄漏通常是由于循环引用造成的。

循环引用指的是两个或多个对象之间互相持有对方的强引用,从而导致它们的引用计数永远不会变为 0,即使它们不再被使用。这时,内存就会一直被占用,从而导致内存泄漏

// Swift5 循环引用
class ClassC {
   var insD: ClassD?
   
   deinit {
       print("deinit ClassC")
   }
   
   func foo() {
       print("func foo in ClassC")
   }
}

class ClassD {
   // 此处引用 ClassC 的实例
   var insC: ClassC?
   
   init(ins: ClassC?) {
       self.insC = ins
   }
   
   deinit {
       print("deinit ClassD")
   }
}

var clzC: ClassC? = ClassC()
var clzD: ClassD? = ClassD(ins: clzC)

clzC?.insD = clzD

// 此处 clzC 所引用的内存并未释放,对应实例被 clzD 的 insC 引用
clzC = nil
// 依然可以调用 clzD 中的 insC 实例的 foo 方法
clzD?.insC?.foo() // func foo in ClassC
// 此时 clzD 的实例依然被 clzC 的 insD 引用,clzC 和 clzD 实例都未被释放
clzD = nil

Swift 弱引用 解决 循环引用 问题

// Swift5 使用 弱引用 解决 循环引用
class ClassE {
    // 弱引用 weak
    weak var insF: ClassF?
    
    deinit {
        print("deinit ClassE")
    }
    
    func foo() {
        print("func foo in ClassE")
    }
}

class ClassF {
    // 此处引用 ClassE 的实例
    var insE: ClassE?
    
    init(ins: ClassE?) {
        self.insE = ins
    }
    
    deinit {
        print("deinit ClassF")
    }
}

var clzE: ClassE? = ClassE()
var clzF: ClassF? = ClassF(ins: clzE)

clzE?.insF = clzF

// 此处 clzE 所引用的内存并未释放,对应实例被 clzF 的 insE 引用
clzE = nil
// 依然可以调用 clzF 中的 insE 实例的 foo 方法
clzF?.insE?.foo() // func foo in ClassE
// 此时 clzF 的实例被 clzE 的 insF 弱引用,会被销毁,clzE 和 clzF 实例都能被释放
clzF = nil // deinit ClassF \n deinit ClassE

Swift 无主引用,针对类型为非 Optional

无主引用是一种类似于弱引用的引用,但是它假设引用的对象始终存在,不需要在对象释放时设置为 nil。在 Swift 中,我们可以使用 unowned 关键字来声明一个无主引用。

// Swift5 无主引用,针对类型为非 Optional
class ClassG {
    // 无主引用 unowned 假定属性不为 nil
    unowned var insH: ClassH
    
    init(ins: ClassH) {
        self.insH = ins
    }
    func foo() {
        print("func foo in ClassG")
    }
    deinit {
        print("deinit ClassG")
    }
}

class ClassH {
    // 此处引用 ClassE 的实例
    var insG: ClassG?
    
    deinit {
        print("deinit ClassH")
    }

}
var clzH: ClassH? = ClassH()
var clzG: ClassG? = ClassG(ins: clzH!)


clzH?.insG = clzG

// 此处 clzG 所引用的内存并未释放,对应实例被 clzH 的 insG 引用
clzG = nil
// 依然可以调用 clzH 中的 insG 实例的 foo 方法
clzH?.insG?.foo() // func foo in ClassG
// 此时 clzH 的实例被 clzG 的 insH 无主引用,会被销毁,clzG 和 clzH 实例都能被释放
clzH = nil // deinit ClassH \n deinit ClassG
  1. Swift 闭包产生的循环引用
// Swift5 闭包产生的循环引用
class ClassJ {
    var field = "field j"
    
    lazy var closure: () -> Void = {
        print(self.field)
    }
    
    deinit {
        print("deinit ClassJ")
    }
}

var objJ: ClassJ? = ClassJ()
objJ?.closure()
// 因为闭包引用了类的成员属性,导致实例无法释放,进而导致闭包无法释放,产生循环引用
objJ = nil // 此处并没有打印 deinit 中信息

Swift 解决闭包产生的循环引用

// Swift5 解决闭包产生的循环引用
class ClassK {
    var field = "field k"
    
    lazy var closure: () -> Void = {
        // 使用捕获列表对 self 进行无主引用的转换
        [unowned self] () -> Void in
        print(self.field)
    }
    
    deinit {
        print("deinit ClassK")
    }
}

var objK: ClassK? = ClassK()
objK?.closure()
objK = nil // deinit ClassK

异常处理

跟其它语言一样,Swift的异常处理是在程序抛出异常后的处理逻辑。 Swift提供了一流的异常抛出、捕获和处理的能力。跟Java语言类似, Swift的异常并不是真正的程序崩溃, 而是程序运行的一个逻辑分支;Swift和Java捕获异常的时序也是一样的。当Swift运行时抛出异常后并没有被处理, 那么程序就会崩溃。

在Swift语言中使用Error表示异常, 作用同Java的Exception类或Object-C的NSError类。

苹果建议使用枚举作为异常类型(为什么不推荐用类或者结构体?答案是枚举数据类型本身就是分成若干种情况,很适合做逻辑分支判断条件)。

  • Swift提供了try、try?、try!、catch、throw、throws关键字处理异常逻辑

了解更多

Swift 自定义异常类型

// Swift5 自定义异常类型
enum CustomError: Error {
    case ErrorOne
    case ErrorTwo
    case ErrorThree
}

print("error")
//throw CustomError.ErrorOne // 抛出的异常未捕获会终止,不会打印 complete
print("complete")

Swift do-catch 捕获异常,try 执行会抛异常的函数

// Swift5 使用 do-catch 捕获异常,try 执行会抛异常的函数
// 通过函数抛出异常
func funcError() throws -> String {
    throw CustomError.ErrorTwo
}

// 使用 do-catch 捕获异常
do {
    // 使用 try 执行可能会抛出异常的函数
    try funcError()
} catch CustomError.ErrorOne {
    print("ErrorOne")
} catch CustomError.ErrorTwo {
    print("ErrorTwo")
} catch CustomError.ErrorThree {
    print("ErrorThree")
}

// 使用 try? 将函数执行的结果映射为 Optional 类型
let result = try? funcError()
if (result == nil) {
    print("exec failed")
}

// try! 强行终止异常的传递,如果发生异常,则程序中断
// try! funcError()

Swift 函数延时执行结构

// Swift5 函数延时执行结构:避免在抛异常的时候,保证某些必须的代码块要执行,如释放资源
func lazyFunc() throws -> Void {

    defer {
        // 函数结束时会得到执行
        print("lazy part of func")
    }
    
    print("exec lazyFunc")
    throw CustomError.ErrorThree
}

// exec lazyFunc
// lazy part of func
try? lazyFunc()

类型判断/转换

Swift 判断值类型

// Swift5 判断值类型
var anyObj: Any = 1
if anyObj is Int {
    print("anyObj's type is Int")
} else if anyObj is String {
    print("anyObj's type is String")
}

Swift 判断引用类型

// Swift5 判断引用类型
class Base {
    var text = "base text"
}
class Sub1: Base {
    var subText1 = "sub1 text"
}
class Sub2: Base {
    var subText2 = "sub2 text"
    func subFunc() {
        print("sub func invoke")
    }
}

Swift 类型转换

// Swift5 类型转换 as , as! , as?
var obj = Base()
var subObj1 = Sub1()
var subObj2 = Sub2()

// Swift 数组中类型为 Base 的都可以存入
var arrayObj: [Base] = [obj, subObj1, subObj2]

for index in 0..<arrayObj.count {
    let obj = arrayObj[index]
    if obj is Sub1 {
        // 如果是 Sub1 就转换为 Sub1 类型,向下转型
        let subObj = obj as! Sub1
        print(subObj.subText1)
    } else if obj is Sub2 {
        let subObj = obj as! Sub2
        print(subObj.subText2)
    } else {
        print(obj.text)
    }
}

# Swift中的扩展(Extension)

扩展可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即逆向建模)。扩展和 Objective-C 的分类很相似。(与 Objective-C 分类不同的是,Swift 扩展是没有名字的。)

Swift 中的扩展可以:

  • 添加计算型实例属性和计算型类属性
  • 定义实例方法和类方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使已经存在的类型遵循(conform)一个协议

Swift 中的扩展不可以:

  • 不能覆盖原有的功能
  • 不能添加存储属性,不能向已有的属性添加属性观察器
  • 不能添加父类
  • 不能添加指定初始化器,不能添加反初始化器 查看更多
extension SomeType {
  // 在这里给 SomeType 添加新的功能
}

                                

在泛型中扩展的应用

// Swift 使用泛型定义方法,打印自身
func printSelf<T>(_ param: T) {
    print(param)
}
printSelf("text")
printSelf(1000)

// Swift 使用泛型定义结构体,实现简单集合
struct List<T> {
    private var datas: [T] = []
    
    mutating func add(_ newEle: T) {
        datas.append(newEle)
    }
    
    mutating func get(_ index: Int) -> T {
        return datas[index]
    }
}

// Swift 使用扩展,给结构体添加一个扩展方法
extension List {
    func getDatas() -> [T] {
        return datas
    }
}

// 定义整型的集合
var list = List<Int>()
// 添加元素
list.add(1)
list.add(2)
list.add(3)
// 读取元素
var ele = list.get(1)
print(ele) // 2

// 调用扩展方法
var datas = list.getDatas()
print(datas) // [1, 2, 3]
                           

泛型

泛型代码让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。

泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift 的 ArrayDictionary 都是泛型集合。你可以创建一个 Int 类型数组,也可创建一个 String 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。

泛型解决的问题

下面是一个标准的非泛型函数 swapTwoInts(_:_:),用来交换两个 Int 值:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

这个函数使用输入输出参数(inout)来交换 ab 的值,具体请参考 输入输出参数

swapTwoInts(_:_:) 函数将 b 的原始值换成了 a,将 a 的原始值换成了 b,你可以调用这个函数来交换两个 Int 类型变量:

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now (someInt), and anotherInt is now (anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”

swapTwoInts(_:_:) 函数很实用,但它只能作用于 Int 类型。如果你想交换两个 String 类型值,或者 Double 类型值,你必须编写对应的函数,类似下面 swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函数:

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

你可能注意到了,swapTwoInts(_:_:)swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 函数体是一样的,唯一的区别是它们接受的参数类型(IntStringDouble)。

在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)

注意

在上面三个函数中,ab 类型必须相同。如果 ab 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 String 类型的变量和一个 Double 类型的变量互换值。试图这样做将导致编译错误。

泛型函数

泛型函数可适用于任意类型,下面是函数 swapTwoInts(_:_:) 的泛型版本,命名为 swapTwoValues(_:_:)

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

swapTwoValues(_:_:)swapTwoInts(_:_:) 函数体内容相同,它们只在第一行不同,如下所示:

func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

泛型版本的函数使用占位符类型名(这里叫做 T ),而不是 实际类型名(例如 IntStringDouble),占位符类型名并不关心 T 具体的类型,但它要求 ab 必须是相同的类型,T 的实际类型由每次调用 swapTwoValues(_:_:) 来决定。

泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 TswapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T的实际类型。

swapTwoValues(_:_:) 函数现在可以像 swapTwoInts(_:_:) 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(_:_:) 函数被调用时,T 所代表的类型都会由传入的值的类型推断出来。

在下面的两个例子中,T 分别代表 IntString

var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107,anotherInt 现在是 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是“world”,anotherString 现在是“hello”

注意

上面定义的 swapTwoValues(_:_:) 函数是受 swap(_:_:) 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 swapTwoValues(_:_:) 函数的功能,你可以使用已存在的 swap(_:_:) 函数。

AnyObject 与 Any

// Swift 使用 AnyObject 类型声明数组,其中可以存放任何引用类型,不可以存放值类型
var arrayAnyObj: [AnyObject] = [obj, subObj1, subObj2]

// Swift 使用 Any 类型声明数组,其中可以存放任何类型,包含 值类型 和 引用类型
var arrayAny: [Any] = [1, "2", true, obj, (0, 0), {(param: Int) -> Int in return param}]

查看更多

Swift 泛型约束

 
// Swift5 添加泛型约束
// 定义一个存放 Base 子类的结构体
struct ObjList<T: Base> {
    private var datas: [T] = []
    
    mutating func add(_ newEle: T) {
        datas.append(newEle)
    }
    
    mutating func get(_ index: Int) -> T {
        return datas[index]
    }
}
var objList = ObjList<Sub1>()
objList.add(subObj1)

// 定义多个泛型约束
class MultipleType<T, R> where T: BaseProtocol, R: SubProtocol {
}


   

协议 (Protocol)

协议定义了适合特定任务或功能部分的方法、属性和其他要求的蓝图。然后,该协议可以被类、结构或枚举采用,以提供这些要求的实际实现。任何满足协议要求的类型都符合该协议。除了指定符合类型必须实现的要求之外,还可以扩展协议以实现其中一些要求或实现符合类型可以利用的其他功能。
协议语法

协议的定义方式与结构体枚举的定义非常相似:

protocol SomeProtocol { 
 // protocol definition goes here 
} 

属性约束

协议可以要求遵循协议的类型提供特定名称和类型的实例属性类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型

  1. 协议要求一个属性必须明确可读的/可读可写的,类型声明后加上 { set get }来表示属性是可读可写的;
  2. 属性要求定义为变量类型,即使用var而不是let。

方法约束

在协议中定义方法,只需要定义当前方法的名称、参数列表和返回值。类遵循了协议,必须实现协议中的方法。
协议中也可以定义初始化方法,当实现初始化器时,必须使用required关键字。
如果一个协议只能被类实现,需要协议继承自AnyObject。如果此时结构体遵守该协议,会报错。
mutating 在方法中改变方法所属的实例。

协议的使用案例


// Swift5 定义协议 protocol,类似于 java, kotlin 中定义接口
protocol BaseProtocol {
    // 实现协议时才指定类型
    associatedtype T
    // 定义普通方法
    func printType(input: T) -> Void
    
    // 定义计算属性
    var field1: T {get}
    var field2: String {get set}
    
    // 定义静态方法
    static func method()
}

// 协议继承,自动继承 BaseProtocol 中的属性和方法
protocol SubProtocol: BaseProtocol {
}
// ClaProtocol 协议只能被类遵守
protocol ClaProtocol: AnyObject {
}

Swift 遵守实现协议

// Swift5 遵守协议并实现
class ClassImpl: BaseProtocol {
    func printType(input: String) {
        print(input)
    }
    // 实现计算属性
    var field1: String {
        get {return "field1"}
        set {}
    }
    var field2: String {
        get {return "field2"}
        set {}
    }
    // 实现静态方法
    static func method() {}
}

// 为类扩展属性和方法
extension ClassImpl {
    var extField: Int {
        get {
            return 100
        }
    }
    
    func extFunc() {
        print("ext func")
    }
}

var implCla = ClassImpl()

implCla.printType(input: "input text")
print(implCla.extField)
implCla.extFunc()

Swift 定义协议可选实现

// 使用 @objc 定义可选实现
@objc protocol OptionalProtocol: AnyObject {
    @objc optional func method();
}

extension OptionalProtocol {
	// 利用扩展,提供默认实现
    func method() {
        print("default method extension")
    }
}

class ClassOptPro: OptionalProtocol {
}

// 调用默认实现
var claOptIns = ClassOptPro()
claOptIns.method()

Swift 增强协议

// Swift 使用多个协议界定参数
protocol ProtocolA {
    var field: String {get set}
}
protocol ProtocolB {
    func method()
}
// 实现多个协议
class ClassImpl : ProtocolA, ProtocolB {
    var field: String = "impl field"
    
    func method() {
        print("impl method")
    }
}
// 使用 & 界定多个协议
func testImpl(impl: ProtocolA & ProtocolB) {
    print(impl.field)
    impl.method()
}

testImpl(impl: ClassImpl())

Swift 高级特性

Swift 独占访问

Swift 内存安全检查:当两个变量访问同一块内存时,会产生独占内存访问限制。
发生读写权限冲突的情况: inout 参数读写冲突 结构体中函数修改成员属性读写冲突 值类型属性读写冲突

inout 参数读写冲突

// 1. Swift inout 参数读写冲突
var inputStr = "input"
func plusSlef1(_ param: inout String) {
    // 在 >= Swift4 版本会抛异常:同时访问0x103ed30a0,但是修改需要独占访问。
    param += inputStr
}
// 调用下面的代码会崩溃
// plusSlef1(&inputStr)

// 同时访问同一个内存地址,造成读写冲突
func plusSlef2(_ param1: inout String, _ param2: inout String) {
    // 在 >= Swift4 版本会抛异常:重叠访问'inputStr',但修改需要独占访问;考虑复制到一个局部变量
    let result = param1 + param2
    print(result)
}
// 调用下面的代码会崩溃
// plusSlef2(&inputStr, &inputStr)

结构体中函数修改成员属性读写冲突

// Swift 结构体中函数修改成员属性读写冲突
struct StructA {
    var field: Int
    
    // mutating 修饰可修改成员属性,inout 开放写访问,会产生读写冲突
    mutating func edit(_ param: inout StructA) {
        field = param.field
    }
}

var structA = StructA(field: 100)
// 调用下面的代码会产生编译错误:
// 1. Inout参数不允许彼此别名
// 2. 重叠访问'structA',但修改需要独占访问;考虑复制到一个局部变量
// structA.edit(&structA)

值类型属性读写冲突

// Swift 值类型属性读写冲突
class ClassA {
    // 定义元组,属于值类型
    var tuple = (key1: 1, key2 : 2)
    
    func test1(_ param1: inout Int, _ param2: inout Int) {
        print(param1, param2)
    }
    
    func test2() {
        // 如果被调用会崩溃,同时访问0x600000667cd0,但是修改需要独占访问。
        test1(&tuple.key1, &tuple.key2)
    }
    
    func test3() {
        // 访问 局部 值变量 可以正常使用
        var localTuple = (key1: 3, key2 : 4)
        test1(&localTuple.key1, &localTuple.key2)
    }
}

let cla = ClassA()
// 调用下面的代码会崩溃
// cla.test2()
cla.test3() // 正常调用,打印 3 4

swift 动态成员查找

我最喜欢 Swift 语言的一个特性是动态成员查找(dynamic member lookup)。虽然我们并不经常使用它,但它通过改进我们访问特定类型数据的方式,显著改善了所提供类型的 API。

了解更多

@dynamicMemberLookup // Swift使用 @dynamicMemberLookup 为类增加动态查找成员的能力
@dynamicCallable // Swift使用 @dynamicCallable 为类增加动态方法调用的能力
class Data {
    var field1: Int = 0
    var field2: String = ""
    
    subscript(dynamicMember member: Int) -> String {
        return "class don't have the field: \(member), type int"
    }
    
    subscript(dynamicMember member: String) -> String {
        return "class don't have the field: \(member), type String"
    }
    
    // 传入一组参数
    func dynamicallyCall(withArguments argArray: [Int]) {
        print("invoke unknown func with args: \(argArray)")
    }
    
    // 传入键值对参数
    func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) {
        let argPairs = pairs.map{ key, value in
            return "\(key): \(value)"
        }
        print(argPairs)
    }
}

let data = Data()
// 当访问不存在的属性时,就会调用对应的 subscript 方法返回对应类型的值
// class don't have the field: someInt, type String. class don't have the field: someString, type String
print(data.someInt, data.someString)


// 调用不存在的方法,把实例当做方法调用
// 传入一组参数
data(1, 2, 3) // invoke unknown func with args: [1, 2, 3]
// 传入键值对参数
data(key1: 1, key2: 2) // ["key1: 1", "key2: 2"]

    

引用

推荐-其他知识点