Swift 5.9 知识整理 Done
2024-04-27 23:07:02 0 举报
AI智能生成
登录查看完整内容
Swift 基础 语法 面试 知识 整理 汇总
作者其他创作
大纲/内容
大纲
编程语言为什么这样命名,本质它是一门语。那么我们写代码是什么,和一部书一样。语法:词法:值:字面量量:变量var、常量let,区别?型:数据类型:分为:值类型:基本数据类型、集合结构体、元组、枚举、区间?;引用类型:类、和闭包?;区别:值类型和引用类型有以下几个方面的区别:数据存储:独立数据副本 共享数据副本赋值方式:深拷贝 浅拷贝线程安全性:安全 不安全内存占用:较高 较低性能:较高 较低易用性:易用 复杂内存泄漏风险:低 高 符:标识符标点:标点符号句法:普通句子、控制流程语句段法:方法小节:扩展(OC中的分类、扩展;Dart中的extension、minix;)节法:类章法:包书法:项目
感悟
子主题
playground的使用
关键字
关
字面量是值
let a = true true就是一个字面量,例如7、8、9这样的数字也是字面量
字面量
值
量是指变量和常量,它们用于存储和处理数据
是什么
使用 let 关键字声明,其值一旦被赋值就不能再改变。适合用于存储不需要改变的数据
常量(Constants)
使用 var 关键字声明,其值可以随时更改。适合用于存储可能会变化的数据。
变量(Variables)
分类
量
bool
布尔
Int
整型
Float
Double
浮点
Character
字符
String
字符串
本质上都是结构体
基本数据类型
在 Swift 中,枚举(Enum)不是结构体,而是一种单独的数据类型。尽管枚举和结构体都属于值类型,但它们之间有着明显的区别。枚举是一种用于定义一组相关值的类型。它可以包含零个或多个成员,并且每个成员都可以关联一个或多个值。与结构体和类不同,枚举成员之间没有直接的关系,而是在逻辑上相关。枚举通常用于定义一组有限的可能性,例如表示状态、选项、错误等。下面是一个简单的枚举示例:```swiftenum CompassPoint { case north case south case east case west}var direction = CompassPoint.northdirection = .east```在这个例子中,`CompassPoint` 是一个枚举,它包含了四个成员:`north`、`south`、`east` 和 `west`。变量 `direction` 的类型是 `CompassPoint`,可以赋值为枚举中的任何一个成员。虽然枚举和结构体都是值类型,但它们的用途和设计目的有所不同。枚举更适合于表示一组相关的值,而结构体则更适合于表示一个具有多个属性的实体。
单独的类型,不是结构体
枚举
在 Swift 中,元组(Tuple)不是结构体。它们是一种轻量级的数据结构,用于在单个值中存储多个相关的值。元组在 Swift 中被广泛用于返回函数多个值、临时组织相关数据等场景。与结构体不同,元组在编译时被视为一个单独的数据类型。你可以使用元组来临时组织数据,而无需为其定义新的自定义类型。元组的类型是根据其包含的元素的数量和类型来确定的,因此元组的类型可以是异构的,即它可以包含不同类型的值。下面是一个元组的例子:```swiftlet person = (name: \"John\
元组
结构体
Array
Dictionary
Set
集合:本质是结构体
整形区间
不可以用在for-in
字符、字符串区间
按照类型分
闭区间
半开区间
let range3 : PartialRangeThrough<Int> = ...5 // 无限根据数组的边界确定
单侧区间
间隔区间
按照区间范围
...闭区间,包含上下限;..<:半开区间,不包含上限;
总结
讲解
在 Swift 中,区间类型(Range Type)不是结构体,而是一种范围的表示形式。Swift 中有两种主要类型的区间:闭区间和半开区间。闭区间使用 `...` 操作符表示,包括了其开始值和结束值。例如:`1...5` 包含了 1、2、3、4 和 5 这五个值。半开区间使用 `..<` 操作符表示,包含了其开始值但不包含结束值。例如:`1..<5` 包含了 1、2、3 和 4 这四个值。虽然区间在 Swift 中被视为一种类型,但它们并不是结构体或类。它们只是一种用来表示范围的语法结构。当你使用区间来迭代数组、集合或者执行其他类似的操作时,它们会被转换为适当的遍历序列,但并不是真正的集合类型。例如,你可以使用区间来遍历一个范围内的数字:```swiftfor i in 1...5 { print(i) // 输出: 1 2 3 4 5}for j in 1..<5 { print(j) // 输出: 1 2 3 4}```在这两个例子中,`1...5` 和 `1..<5` 都是区间类型,它们并不是结构体或其他值类型。它们只是用来表示范围的语法结构。
是结构体吗?不是
单独类型,非结构体
区间类型
值类型
类
在 Swift 中,闭包类型不是类,它们是一种特殊的函数类型。虽然闭包在某种程度上类似于匿名函数,但它们不同于类或结构体。闭包在 Swift 中具有自己的类型,由于 Swift 是一种强类型语言,因此闭包类型是编译时确定的。你可以像声明其他变量和常量类型一样声明闭包类型,并将闭包类型作为函数的参数、返回值或者赋值给变量或常量。在 Swift 中,闭包类型的声明通常是这样的:```swift(parameters) -> ReturnType```其中 `parameters` 是闭包的参数列表,`ReturnType` 是闭包的返回类型。例如:```swift() -> Void```表示没有参数且没有返回值的闭包类型。虽然闭包类型与类或结构体类型不同,但你可以像操作其他类型一样对闭包类型进行操作,例如传递给函数、从函数返回、赋值给变量等。这使得闭包在 Swift 中成为一等公民,具有与其他类型相同的地位和灵活性。
闭包是类吗
问题
闭包
详见03、中的函数类型子分支
函数类型
详介
引用类型
根据赋值方式分为值类型和引用类型
类类型
元组类型
根据类型种类分五类
详见05、可选类型
根据是否可赋空分为可选类型和非可选类型
泛型本身不是值类型也不是引用类型。泛型类型可以包含值类型或引用类型。当包含值类型的泛型类型被赋值给新变量或传递给函数时,其值会被复制。当包含引用类型的泛型类型被赋值给新变量或传递给函数时,其值会被引用。
所有类型的类型:泛型类型
类型转换是指将一种类型的值转换为另一种类型的值的操作。类型转换可以分为隐式类型转换和显式类型转换。**隐式类型转换**隐式类型转换是指编译器自动执行的类型转换。在大多数情况下,隐式类型转换是安全的,因为编译器会确保转换后的值不会丢失精度或导致数据溢出。以下是一些常见的隐式类型转换:* 将较小的整数类型转换为较大的整数类型,例如将 `Int8` 转换为 `Int32`。* 将浮点数类型转换为整数类型,例如将 `Int` 转换为 `Double`。* 将字符类型转换为整数类型,例如将 `Character` 转换为 `UInt8`。**显式类型转换**显式类型转换是指程序员使用强制类型转换运算符(`as` 或 `as?`)来指定类型的转换。显式类型转换可以用于将一种类型的值转换为另一种类型,即使转换可能导致数据丢失或溢出。以下是一些常见的显式类型转换:* 将 `Int` 类型的值转换为 `String` 类型,例如:```swiftlet number = 10let string = String(number)```* 将 `String` 类型的值转换为 `Int` 类型,例如:```swiftlet string = \"10\"let number = Int(string)```**类型转换的注意事项*** 在使用隐式类型转换时,需要注意转换后的值是否会丢失精度或导致数据溢出。* 在使用显式类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。* 在使用类型转换时,需要考虑代码的可读性和可维护性。**以下是一些使用类型转换的示例:**```swift// 将 Int 类型的值转换为 String 类型let number = 10let string = String(number)// 将 String 类型的值转换为 Int 类型let string = \"10\"let number = Int(string)// 将 Double 类型的值转换为 Int 类型let doubleValue = 3.14let intValue = Int(doubleValue) // 结果为 3// 将 Int 类型的值转换为 Double 类型let intValue = 10let doubleValue = Double(intValue) // 结果为 10.0// 使用 as? 进行类型转换let string = \"123\"let number = Int(string) // 隐式类型转换,结果为 123let string2 = \"abc\"let number2 = Int(string2) // 隐式类型转换,结果为 nillet number3 = string2 as? Int // 显式类型转换,结果为 nil```**总结:*** 类型转换是将一种类型的值转换为另一种类型的值的操作。* 类型转换可以分为隐式类型转换和显式类型转换。* 在使用类型转换时,需要注意转换是否安全,是否会导致数据丢失或溢出。* 理解类型转换可以帮助您编写更优雅、更高效、更安全的 Swift 代码。
```在 Swift 中,将 Double 转换为 Int 有五种常见的方法:as和as?、构造方法、数学函数、自定义函数以及第三方库```**1. 使用强制类型转换运算符 `as` 或 `as?`*** `as` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。* `as?` 运算符会将 Double 值转换为 Int 值,如果转换失败,则结果为 nil。**示例:**```swiftlet doubleValue: Double = 3.14// 使用 as 运算符let intValue = doubleValue as Int // 结果为 3// 使用 as? 运算符let intValue2 = doubleValue as? Int // 结果为 3let doubleValue2: Double = \"abc\"// 使用 as 运算符let intValue3 = doubleValue2 as Int // 结果为 nil// 使用 as? 运算符let intValue4 = doubleValue2 as? Int // 结果为 nil```**2. 使用初始化器*** 可以使用 Double 的初始化器将 Int 值转换为 Double 值。示例:```Swiftlet intValue: Int = 10// 使用 Double 的初始化器let doubleValue5 = Double(intValue) // 结果为 10.0```**3. 使用数学函数*** 使用 `floor()` 函数可以将 Double 值向下取整为 Int 值。* 使用 `ceil()` 函数可以将 Double 值向上取整为 Int 值。* 使用 `round()` 函数可以将 Double 值四舍五入为 Int 值。**示例:**```swiftlet doubleValue: Double = 3.14// 使用 floor() 函数let intValue5 = floor(doubleValue) // 结果为 3// 使用 ceil() 函数let intValue6 = ceil(doubleValue) // 结果为 4// 使用 round() 函数let intValue7 = round(doubleValue) // 结果为 3```**4. 使用自定义函数**您可以定义一个自定义函数来执行 Double 到 Int 的转换。例如:```swiftfunc doubleToInt(_ doubleValue: Double) -> Int { return Int(floor(doubleValue))}let doubleValue = 3.14// 使用自定义函数let intValue8 = doubleToInt(doubleValue) // 结果为 3```**5. 使用第三方库**您可以使用第三方库来执行 Double 到 Int 的转换。例如,`Numeric` 库提供了一个 `toInt()` 方法可以将 Double 值转换为 Int 值。```swiftimport Numericlet doubleValue = 3.14// 使用第三方库let intValue9 = doubleValue.toInt() // 结果为 3```**选择哪种方法取决于您的具体需求:*** 如果您需要确保转换成功,请使用 `as` 运算符。* 如果您需要处理转换失败的情况,请使用 `as?` 运算符。* 如果您需要对 Double 值进行取整或四舍五入,请使用相应的数学函数。* 如果您需要更复杂的转换逻辑,可以定义自定义函数。* 如果您需要使用第三方库提供的功能,可以使用第三方库。**总结:*** 在 Swift 中,将 Double 转换为 Int 有多种方法。* 选择哪种方法取决于您的具体需求。* 理解如何将 Double 转换为 Int 可以帮助您编写更优雅、更高效、更安全的 Swift 代码。**注意:*** 将 Double 转换为 Int 时可能会丢失精度。* 如果 Double 值太大或太小,转换可能会导致溢出。
类型转换的五种方法
范围大的类型转换为范围小的类型,比如将 `Double` 转换为 `Int`
什么情况下需要强制类型转换
类型转换
答:## 值类型和引用类型在 Swift 中,类型可以分为值类型和引用类型。### 值类型**特点:*** **独立性:** 值类型的每个实例都拥有自己的独立数据副本。* **深拷贝:** 当值类型被赋值给新变量或传递给函数时,其值会被深拷贝,即新变量或函数参数将获得该值的独立副本。* **线程安全性:** 值类型在多线程环境下是安全的,因为每个线程都操作的是该值的独立副本。**优势:*** **性能:** 值类型的赋值和传递操作效率更高,因为不需要额外的引用计数和内存管理。* **内存安全性:** 值类型避免了引用类型常见的循环引用问题,可以减少内存泄漏的风险。* **易用性:** 值类型的语义更加直观易懂,可以使代码更易于理解和维护。**劣势:*** **内存占用:** 值类型在某些情况下可能占用更多的内存,因为每个实例都拥有自己的独立数据副本。* **性能开销:** 值类型的深拷贝操作在某些情况下可能带来性能开销。**示例:*** 基本类型:`Int`、`Double`、`Float`、`String`、`Bool`* 结构体(`struct`)* 枚举(`enum`)* 元组(`tuple`)### 引用类型**特点:*** **共享性:** 引用类型的多个实例可以共享同一个数据副本。* **浅拷贝:** 当引用类型被赋值给新变量或传递给函数时,其值会被浅拷贝,即新变量或函数参数将获得对同一个数据副本的引用。* **线程不安全性:** 引用类型在多线程环境下可能存在线程安全问题,因为多个线程可能操作同一个数据副本。**优势:*** **内存占用:** 引用类型在某些情况下可以节省内存,因为多个实例可以共享同一个数据副本。* **性能优化:** 引用类型的浅拷贝操作在某些情况下可以提高性能。**劣势:*** **内存泄漏:** 引用类型容易出现循环引用问题,导致内存泄漏。* **线程安全性:** 引用类型在多线程环境下需要额外的同步机制来保证线程安全性。* **复杂性:** 引用类型的语义相对复杂,可能使代码更难理解和维护。**示例:*** 类(`class`)* 闭包(`closure`)### 区别| 特性 | 值类型 | 引用类型 ||---|---|---|| 数据存储 | 独立数据副本 | 共享数据副本 || 赋值方式 | 深拷贝 | 浅拷贝 || 线程安全性 | 安全 | 不安全 || 内存占用 | 较高 | 较低 || 性能 | 较高 | 较低 || 易用性 | 易用 | 复杂 || 内存泄漏风险 | 低 | 高 || 示例 | `Int`、`String`、`struct` | `class`、`closure` |**总结:*** 值类型和引用类型是 Swift 中两种重要的类型。* 它们在数据存储、赋值方式、线程安全性、内存占用、性能、易用性、内存泄漏风险等方面存在差异。* 选择合适的类型可以提高代码的性能、安全性、可维护性和可读性。**建议:*** 首选值类型,除非有明确的理由使用引用类型。* 使用引用类型时,需要谨慎考虑线程安全问题和内存泄漏风险。
问:值类型和引用类型各自的优缺点,以及他们的区别是什么
总结:一般情况下,值类型存储在栈上,而引用类型存储在堆上,但并不是绝对的规则。在编译器和运行时的优化过程中,可能会对存储位置进行优化或变化。不完全正确。在 Swift 中,值类型和引用类型的存储位置并不是一定的,而是取决于它们的具体情况和上下文。1. **值类型(Value Types)**: - 值类型包括结构体(Structures)、枚举(Enumerations)和基本数据类型(比如整数、浮点数、布尔值等)。 - 值类型通常存储在栈上,但在某些情况下也可能存储在堆上,比如在闭包中捕获值类型的情况下,或者在进行了值类型的拷贝优化时。 - 栈上的存储使得值类型的内存管理更加高效,因为它们的生命周期是与其所在的作用域密切相关的。2. **引用类型(Reference Types)**: - 引用类型包括类(Class)和闭包(Closure)。 - 引用类型通常存储在堆上。这是因为它们的内存分配和释放由引用计数(Reference Counting)来管理,以确保内存的正确释放。 - 当我们创建一个类的实例时,实例本身存储在堆上,而栈上存储的是对实例的引用。
问:值类型一定存储在栈上,引用类型一定存储在堆上吗
题:
数据类型
型
标点符号
符
01、基础语法:语句构成五要素
在 Swift 中,`if-else` 是用于进行条件判断的基本语句。下面是 `if-else` 的基本使用方法:```swiftlet number = 10if number > 0 { print(\"Number is positive\")} else if number < 0 { print(\"Number is negative\")} else { print(\"Number is zero\")}```在上面的例子中,首先定义了一个常量 `number`,然后使用 `if-else` 结构来检查 `number` 的值。如果 `number` 大于 0,则打印 `\"Number is positive\"`;如果 `number` 小于 0,则打印 `\"Number is negative\"`;如果 `number` 等于 0,则打印 `\"Number is zero\"`。此外,`if-else` 结构也可以嵌套使用,例如:```swiftlet a = 10let b = 20if a > 0 { if b > 0 { print(\"Both a and b are positive\") } else { print(\"a is positive but b is not\") }} else { print(\"a is not positive\")}```在这个例子中,我们检查了两个变量 `a` 和 `b` 的值。如果 `a` 大于 0,则进一步检查 `b` 的值,如果 `b` 也大于 0,则打印 `\"Both a and b are positive\"`;如果 `b` 不大于 0,则打印 `\"a is positive but b is not\"`。如果 `a` 不大于 0,则直接打印 `\"a is not positive\"`。这就是 Swift 中使用 `if-else` 进行条件判断的基本方法。
if-else
在 Swift 中,`while` 是一种循环语句,用于重复执行一段代码,直到指定的条件不再满足为止。`while` 循环会在每次循环开始之前检查条件是否为真,如果条件为真,则执行循环体中的代码;如果条件为假,则退出循环。`while` 循环的基本语法如下所示:```swiftwhile condition { // 循环体}```在这个语法中,`condition` 是一个布尔表达式,表示循环的条件。只要 `condition` 为真,循环就会继续执行;一旦 `condition` 为假,循环就会终止,程序会继续执行循环后的代码。下面是一个简单的示例,演示了如何使用 `while` 循环计算从 1 到 5 的数字的和:```swiftvar sum = 0var number = 1while number <= 5 { sum += number number += 1}print(\"Sum is \\(sum)\")```在这个示例中,`while` 循环会在 `number` 的值小于或等于 5 的情况下重复执行循环体。在每次循环中,将 `number` 添加到 `sum` 中,并递增 `number` 的值。当 `number` 的值超过 5 时,循环终止,最后打印出计算得到的和。`while` 循环是 Swift 中常用的控制流语句之一,它提供了一种灵活的方式来重复执行代码,直到特定条件不再满足为止。
while
相当于c语言的do-while
repeat-while
++for
0、1、2、3
[0...3]
0、1、2
[0..<3]
for name in names[0...3] {}
遍历数组元素
0...3
0..<3
for i in 0...3 { // i = 0\\1\\2\\3}
遍历整型区间
例子
for-in
for
支持多类型比较、值绑定、条件判断
改进
没有默认穿透,不可以使用默认穿透,如果需要穿透可以使用fallthrough
break可以省略
如 case \"a\
case 符合条件
这个强大了尤其是配合元组,无敌了简直
不仅支持整形,也支持字符、字符串、区间、元组匹配
必要时let也可以改为var
举例
支持值绑定
元组+值绑定+条件判断,这个厉害了
支持条件判断
default是非必须的
must exhaustive. 保证switch处理了所有的情况
case、default后面至少要有一条语句,可以是break,因此使用不了默认穿透。switch没有默认穿透。
和oc的差异与增强
switch-case
举例参考02-流程控制
类似goto
标签语句
02、控制流程:wrifs
在Swift中,函数的定义遵循一般的语法规则。下面是一个简单的函数定义示例:```swiftfunc greet(name: String) -> String { return \
函数的定义
在Swift中,如果函数体只有一行表达式,并且返回类型可以通过表达式的类型推断出来,你可以使用隐式返回。这意味着你可以省略 `return` 关键字,而直接将表达式写在函数体内。下面是一个使用隐式返回的例子:```swiftfunc double(_ number: Int) -> Int { number * 2}```在这个例子中,我们定义了一个名为 `double` 的函数,它接受一个整数参数 `number`,并返回这个参数的两倍。函数体只有一行表达式 `number * 2`,因此可以省略 `return` 关键字。你可以像平常一样调用这个函数:```swiftlet result = double(5)print(result) // 输出: 10```这里的 `result` 将会是 `10`,因为 `double(5)` 返回 `5` 的两倍,即 `10`。
隐式返回
返回元组:实现多返回值(这个爽的一笔)
函数的文档注释
参数的标签
参数的默认值
可变参数
介绍
使用的场景,传入参数为使用的&是什么意思
可变参数不能标记为inout
inout参数不能有默认值
inout参数只能传入可以被多次赋值的
inout参数的本质是地址传递(引用传递)
注意
输入输出参数
参数个数不同
参数类型不同
参数标签不同
函数名称相同基础上满足一下条件均可重载
规则
返回值类型与函数重载无关
默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在c++中会报错),这种情况要注意了。
可变参数、省略参数标签、函数重载一起使用产生二义性时,编译器有可能会报错
函数的重载
控制台输出函数print的使用Swift提供了许多内置函数,这些函数不需要显式导入,可以直接在你的代码中使用。下面是一些常用的内置函数:1. `print(_:separator:terminator:)`:打印信息到控制台。```swiftprint(\
内置函数
//永远不会被内联(即使开启了编译器优化)
@inline(never) func test () -> Void {}
// 开启编译器优化后,即使代码很长,也会被内联(递归函数、动态派发的函数除外)
@inline(__always) func test () {}
函数体比较长
包含递归调用
包含动态派发
开启编译器优化后,系统自动进行内联优化,一般一下情况不进行内联优化
@inline func test() {}
@inline
在 Swift 中,内联函数(inline functions)是指在编译时将函数调用直接替换为函数体的一种优化方式。内联函数可以提高代码的执行效率,因为它减少了函数调用的开销,特别是对于频繁调用的小型函数而言。Swift 并没有提供显式的关键字来声明函数为内联函数,而是由编译器自动决定是否将函数进行内联。编译器会基于函数的大小、上下文和其他因素来决定是否内联函数。通常情况下,Swift 会自动将简短的函数内联。例如,一个简单的函数,它的函数体很短,可能被频繁调用,这种情况下编译器会倾向于将其内联。另外,在某些情况下,你可以使用 `@inline(__always)` 属性来强制要求编译器将函数内联。这样做通常是出于优化的目的,但需要注意过度使用内联可能会导致代码体积增加,因此应谨慎使用。```swift@inline(__always)func inlineFunction() { // 函数体}```需要注意的是,虽然内联函数可以提高执行效率,但在某些情况下它可能会增加代码大小。因此,在使用 `@inline(__always)` 强制内联函数时,需要仔细权衡,确保它带来的性能提升能够超过因代码增大而带来的额外开销。
内联函数
形式参数类型
返回值类型
由两部分组成
函数类型作为函数参数例子
类型别名(typealias)
在 Swift 中,`Void` 是一种空元组类型,表示一个不包含任何值的元组。因此,`Void` 类型实际上等同于空元组 `()`。 你可以将 `Void` 看作是一个空的容器,它不包含任何数据。在函数声明中,如果函数不返回任何值,则通常会使用 `Void` 类型作为返回类型,以表示该函数不返回任何值。举个例子,在函数中如果没有明确指定返回值,你可以使用 `Void` 类型来表示这个函数不返回任何内容:```swiftfunc printHello() -> Void { print(\"Hello\")}func doSomething() -> () { // 这里是函数体,没有返回任何内容}```或者,你可以直接省略返回类型,Swift 会自动推断为 `Void`:```swiftfunc printHello() { print(\"Hello\")}```在其他上下文中,你也可以使用空元组 `()` 来表示 `Void` 类型。例如,作为元组的类型标注,或者作为泛型类型参数的占位符。总的来说,`Void` 类型是一种在 Swift 中用来表示空值或没有值的方式,通常用于表示不返回任何内容的函数或泛型类型。
源码中的定义
public typealias Void = ()
Void本质是空元组
Void本质
在 Swift 中,你可以在一个函数内部定义另一个函数,这种函数称为嵌套函数(Nested Functions)。嵌套函数可以访问外部函数的变量,甚至可以访问外部函数的参数。嵌套函数在外部函数的作用域内有效,但它们不能被外部函数之外的代码调用。以下是一个简单的示例:```swiftfunc outerFunction() -> Int { var x = 10 // 嵌套函数 func innerFunction(y: Int) -> Int { return x + y } return innerFunction(y: 5)}let result = outerFunction()print(result) // 输出: 15```在这个例子中,`innerFunction` 是 `outerFunction` 的嵌套函数。它可以访问 `outerFunction` 中的变量 `x`,并且可以使用外部函数的参数 `y`。在 `outerFunction` 中调用 `innerFunction` 并返回结果。嵌套函数的一个常见用途是在一个函数中定义一个辅助函数,这样可以使代码更加模块化和易于理解。此外,由于嵌套函数只在外部函数中可见,这样可以避免污染全局命名空间。
可以根据入参,调用内部定义的不同的函数。
可以用来做什么
将函数定义在函数内部
总结:封装思想、迪米特法则的体现
嵌套函数
03、函数
在 Swift 中,枚举(Enumeration)是一种用于定义一组相关值的通用数据类型。枚举类型允许你以清晰明了的方式来表示一组有限的可能性。枚举可以用于在代码中定义一组特定的值,并且在需要时可以轻松地引用这些值。枚举在Swift中是非常强大且常用的特性之一,你可以使用它来定义特定的状态、选项、错误类型等。Swift 中的枚举与其他编程语言中的枚举有些相似,但也有一些特殊的特性,例如:1. **支持关联值**:Swift 的枚举可以关联额外的值,使得枚举成员能够携带更多的信息。这使得枚举更加灵活,可以应对更多的场景。2. **支持原始值**:Swift 的枚举可以关联原始值,每个枚举成员都有一个预先定义的原始值。原始值可以是整数、浮点数、字符串或者其他一些类型。3. **支持方法和计算属性**:你可以在 Swift 的枚举中定义方法和计算属性,使得枚举更加强大。4. **支持协议**:Swift 的枚举可以遵循协议,从而使其更加灵活,可以适应不同的需求。5. **支持迭代**:通过遵循`CaseIterable`协议,你可以让枚举变得可迭代,从而可以轻松地遍历枚举中的所有成员。在 Swift 中,枚举可以用于多种情况,例如表示状态、选项、错误类型等。它提供了一种清晰、安全且灵活的方式来组织代码和数据。以下是一个简单的枚举示例以及如何使用它:```swift// 定义一个名为CompassPoint的枚举enum CompassPoint { case north case south case east case west}// 使用枚举var directionToGo = CompassPoint.westdirectionToGo = .east// 使用switch语句匹配枚举值switch directionToGo {case .north: print(\"Going North\")case .south: print(\"Going South\")case .east: print(\"Going East\")case .west: print(\"Going West\")}```在这个例子中,我们定义了一个名为`CompassPoint`的枚举,它包含了四个可能的方向:北、南、东、西。然后,我们创建了一个变量`directionToGo`,并将其设置为枚举成员`CompassPoint.west`。接着,我们使用点语法将变量设置为`CompassPoint.east`。最后,我们使用`switch`语句来匹配枚举值,并打印出相应的信息。除了简单的枚举之外,Swift 还支持枚举关联值和原始值,使得枚举更加灵活和强大。例如,你可以在枚举成员中存储额外的相关数据,或者为枚举成员指定预定义的原始值。在实际开发中,你可以使用枚举来表示应用程序中的各种状态、选项或者其他有限的可能性。Swift 的枚举是一种强大的工具,可以帮助你编写更清晰、更安全的代码。
枚举的基本用法
概念以及使用方法
关联值
枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值
原始值不占用枚举变量的内存
0...n
\"枚举成员的变量名的字符串\"
如果枚举的原始值类型是Int String,swift会自动分类原始值。
隐式原始值
原始值
indirect(间接的)
arith(算术)
递归枚举
MemoryLayout
获取枚举占用的内存大小
思考枚举的内存布局
进一步观察下枚举的内存布局
switch语句的底层是如何实现的?
拔高
04、枚举
在类型名称后面加一个问号?来定义可选类型。
var name: String? = \"Jack\" // name = nil is valid
列子
语法
在 Swift 中,可选项(Optionals)是一种特殊的类型,用于处理可能缺失值的情况。在某些情况下,变量或常量可能没有值,此时可以使用可选项来表示这种可能性。可选项的声明使用 `?` 符号,例如:```swiftvar optionalInt: Int?```这表示 `optionalInt` 是一个可选的整数。它可以存储一个整数值,也可以不存储任何值(即 `nil`)。你可以使用 `if let` 或 `guard let` 语句来解包可选项,以安全地访问其中的值:```swiftvar optionalInt: Int? = 10if let unwrappedInt = optionalInt { print(\"The value of optionalInt is \\(unwrappedInt)\")} else { print(\"optionalInt is nil\")}```或者使用可选项的强制解包(Force Unwrapping):```swiftvar optionalInt: Int? = 10let unwrappedInt = optionalInt!print(\"The value of optionalInt is \\(unwrappedInt)\")```需要注意的是,强制解包在可选项为 `nil` 的情况下会导致运行时错误,因此应该谨慎使用。最好使用 `if let` 或 `guard let` 来安全地解包可选项。此外,Swift 还提供了可选项的隐式解包(Implicitly Unwrapped Optionals),它们在声明时使用 `!` 符号,表示可选项在使用时会自动解包。通常情况下,隐式解包可选项用于确保一个变量或常量在被使用之前已经被初始化,并且永远不会为 `nil`。
声明的变量,允许被设置为nil
如果为nil,它是个空盒子
如果不为nil,那么盒子里装的是:被包装类型的数据。
可选性是对其他类型的一层包装,可以将它理解为一个盒子
可选类型
let i:Int i = 1 // i 不能被赋空,编辑器会报错
非可选类型
类型根据是否可选,即是否可以赋空,类型又被分为可选类型和非可选类型两种
如果要从可选选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号!进行强制解包如果对值为nil的可选项进行强制解包,将会产生运行时错误Fatal error:unexpectedly found nil while unwarpping an Optional value
if obj != nil
while循环中使用可选项绑定
判断可选类型是否包含值
强制解包
可选项到
为了干什么
`if let` 是 Swift 中用于安全解包可选值的一种常见模式,用于检查可选值是否包含非 nil 的值,并且在包含值的情况下将其解包并赋值给一个新的常量或变量。```swiftvar optionalString: String? = \"Hello\"if let unwrappedString = optionalString { // optionalString 包含值,且被解包赋值给 unwrappedString print(\"The string is: \\(unwrappedString)\")} else { // optionalString 为 nil print(\"The string is nil\")}```在上面的例子中,`if let` 语句用于检查 `optionalString` 是否包含一个非 nil 的值。如果 `optionalString` 包含值,则该值将被解包并赋值给 `unwrappedString`,然后执行 `if` 语句中的代码块。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行。`if let` 的优势在于它提供了一种清晰且安全的方式来处理可选值,避免了强制解包可能导致的运行时错误。因此,它通常用于可选值的处理,特别是在需要使用可选值的情况下。
if let unwrapped = optinal {}
单条件写法
if let first = Int(\"4\
多条件合并层写法:逗号隔开
if let
`guard let` 是 `guard` 语句的一个特定用法,通常用于对可选值进行解包,并且在解包失败时提前退出函数、方法、循环或代码块。```swiftfunc processString(optionalString: String?) { guard let unwrappedString = optionalString else { print(\"The string is nil\") return } // 在这里可以安全地使用 unwrappedString,它是一个非可选的 String 值 print(\"The string is: \\(unwrappedString)\")}```在上面的例子中,`guard let` 语句用于解包 `optionalString`,并将其非可选值赋给 `unwrappedString`。如果 `optionalString` 是 nil,则 `else` 分支中的代码块将被执行,输出 \"The string is nil\",然后函数提前退出。如果 `optionalString` 不是 nil,则继续执行 `guard` 语句后面的代码,安全地使用 `unwrappedString`。`guard let` 的优点在于它提供了一种简洁的方式来处理可选值,并且可以减少嵌套的代码结构。这使得代码更易读,同时确保了可选值的安全处理。
guard 条件 else {// do something // 退出当前作用域 // return break、continue、throw error }
当guard语句的条件为false时,就会执行大括号里面的代码
当guard语句的条件为true时,就会跳过guard语句
含义
特别适合,满足条件再往下执行,否则提前退出的场景
场景
这点真特码的爽
当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用于中使用。
巧用
guard(警卫)规则
单条件写法:guard let unwrapped = optinal else {}
guard let first = Int(\"4\
多条件和并写法:逗号隔开
guard let
a??b
a是可选项
b是可选项或者不是可选项
b跟a的存储类型必须相同
如果a不为nil,就返回a
如果a为nil,就返回b
在 Swift 中,双问号 `??` 是一个空合并运算符。它用于提供一个默认值,当左边的可选值为 nil 时,就返回右边的值。但是,如果右边的值不是可选类型,而左边的值是可选类型,那么在编译时会发生错误,因为它们的类型不匹配。如果你想要使用空合并运算符,并且右边的值不是可选类型,你可以在左边的可选值上使用强制解包来进行操作,像这样:```swiftvar optionalValue: Int? = nillet defaultValue = 10let result = optionalValue ?? defaultValue```在这个例子中,如果 `optionalValue` 是 nil,`result` 将会是 `defaultValue`,否则它将会是 `optionalValue` 解包后的值。
这个很重要,这个很重要
如果b不是可选项,返回a时会自动解包
a规则不变,c到n与b规则相同
a??b??c
多个?? 一起使用
??跟if let 配合使用:if let c = a ?? b {}
空合并运算符??
方法
如果包含就自动解包,把值赋值给一个临时的常量(let)或者变量(var),并返回true,否在返回false。
可选项绑定(optional binding)
安全解包:可选项绑定解包
在某些情况下,可选项一旦被设定值之后,就会一直用有值,在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它确定每次访问的是时候都有值
可以再类型后面加一个感叹号!定义一个隐式解包的可选项
类型后边加一个感叹号!
隐式解包(Implicitly unwrapped optional)
解包
\\(age!)
隐式解包
\\(String(describing:age))
String 描述方法
\\(age ?? 0)
空合并运算符
字符串插值与解包
oh no ,bye bye,拒绝套娃🪆
done:使用查看内存结构的指令查看不同看是否可以.(fr v -R)
断点场景下,可用如下lldb命令 : “fr v -R 属性名” 查看可选项结构。 便于调试时查看可选项。(普通可选项、强制可选项、多可选项)
关于swift可选项(?)小技巧:
参考辅助图解
生僻
(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 } }}
var num1: Int? = nilvar num2: Int?? = num1var num3: Int?? = nilprint(num2 == num3) // false(num2 ?? 1) ?? 2 // 2(num3 ?? 1) ?? 2 // 1
多重可选项
05、可选类型
比如:Bool、Int、Double、String、Array、Dictionary等产检类型都是结构体
在swift标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分。
背景
所有结构体都有一个编译器自动生成的初始化器(initializer,初始化方法、构造器、构造方法)
多个初始化器
特点:编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员都有初始化值。
可选项与自动创建初始化器
可选项有默认值nil,因此在自动创建初始化器的时候会依据有默认值来判定,创建多个初始化器。
自动化生成初始化器
一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
自定义初始化器
声明
struct Point {var x: Int = 0var y: Int = 0var origin: Bool = false}print(MemoryLayout<Point>.size)//17print(MemoryLayout<Point>.stride)//24print(MemoryLayout<Point>.alignment)//8
查看方法
内存结构
类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器。
只有类的所有成员都在定义的时候指定了初始值,编译器会为类自动创建无参的初始化器。传参初始化器,需要开发人员手动创建。
只有当类的所有成员都有初始值时,编译器会自动创建。
在 Swift 中,类与结构体和枚举不同,它们有一些额外的初始化器行为。Swift 中的类如果没有定义任何初始化器,编译器会为你自动生成一个无参的 designated 初始化器。但是,你也可以手动创建无参初始化器。下面是一个示例,演示了如何手动创建一个无参初始化器:```swiftclass MyClass { var value: Int // 手动创建无参初始化器 init() { value = 0 }}// 使用无参初始化器创建实例let myInstance = MyClass()print(myInstance.value) // 输出: 0```在这个示例中,`MyClass` 类具有一个存储属性 `value`,并且我们手动创建了一个无参初始化器来初始化这个属性。
可以手动创建无参初始化器吗?可以
无参初始化器
编译器不会自动创建,需要自己手动创建。
有参初始化器
便于记忆
初始化器的背景
类的初始化器的使用
静态属性、方法、下标的访问方法和类的属性、方法、下标一样
类中的属性、方法、下标分为实例、类、静态三种
如图
类型不同,结构体是值类型,类是引用类型
结构体和类的本质区别
理解:类似于对文件进行copy、paste操作,产生了全新的文件副本。属于深拷贝(deep copy)
值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
比如仅当有“写”操作时,才会真正的执行拷贝操作
对于标准库值类型的赋值操作,swift能确保最佳性能,所以没必要为了保证最佳性能类避免赋值。
在swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术
不需要修改的,尽量定义成let
建议
单个变量多次赋值
一个变量赋值给另外一个变量
本质
赋值操作
赋值和传参操作
类似于制作文件的替身(快捷方式),指向的是同意文件。属于浅拷贝(shallow copy)
引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
图解
一个变量赋值给另一个变量
对象的堆空间申请过程
值类型和引用类型的let
struct 嵌套枚举
嵌套类型
这个酷,尤其是枚举增强,有利于减少代码的冗余
一般把定义在枚举、结构体、类内部的函数,叫做方法。
方法的本质就是函数
方法、函数都存放在代码段
不占用
对象的方法占用内存吗
在 Swift 中,类中的方法存储在虚函数表 (VTable) 中。VTable 是一张方法地址表,它包含了类中所有虚方法的地址。当调用一个虚方法时,编译器会根据对象的类型查找 VTable 中对应的方法地址,然后跳转到该地址执行方法。具体来说,在 Swift 中,类的方法存储位置如下:* **实例方法:**实例方法存储在类的 VTable 中。VTable 是一个指针数组,每个元素指向一个实例方法的地址。当调用一个实例方法时,编译器会根据对象的类型查找 VTable 中对应的方法地址,然后跳转到该地址执行方法。* **类方法:**类方法存储在类的元类 (metaclass) 的 VTable 中。元类是类的类,它也是一个类对象。类方法的调用方式与实例方法类似,只不过编译器会查找元类的 VTable 而不是对象的 VTable。* **静态方法:**静态方法存储在代码段中。静态方法不属于任何类或对象,可以直接通过类名或方法名调用。以下是一些关于 Swift 中类方法存储位置的额外信息:* **final 修饰的方法:**final 修饰的方法是静态方法,它们存储在代码段中。* **@objc dynamic 修饰的方法:**@objc dynamic 修饰的方法是动态方法,它们存储在 VTable 中。* **扩展中的方法:**扩展中的方法不存储在 VTable 中,它们是静态方法,存储在代码段中。希望这些信息对您有所帮助。
对象的方法存储在哪里
## Swift 实例方法、类方法和静态方法**1. 实例方法****定义:** 实例方法属于类的**实例**,而非类本身。这意味着实例方法必须**依附于类的实例**才能被调用。**语法:**```swiftclass <类名> { // 实例方法 func <方法名>(参数列表) -> 返回值类型 { // 方法体 }}```**特点:*** 可以访问**实例属性**和**实例方法*** 可以修改**实例变量*** 必须通过**类的实例**调用**示例:**```swiftclass Person { var name: String // 实例方法 func sayHello() { print(\
swift 分别讲解下实例方法、类方法、静态方法,并谈谈他们的区别是什么,以及存储的位置是否相同
元类存储在**代码段**中。代码段是一块连续的内存区域,它包含了应用程序的代码和数据。代码段中的代码可以直接由 CPU 执行。在 Swift 中,元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。以下是元类存储位置的详细信息:* **类对象:**类对象存储在堆栈中。堆栈是一块内存区域,用于存储程序的临时数据。类对象在程序运行时创建,并在程序结束时销毁。* **元类:**元类存储在代码段中。代码段是一块连续的内存区域,它包含了应用程序的代码和数据。代码段中的代码可以直接由 CPU 执行。**以下是一些示例:**```swiftclass MyClass { // 类方法 class func classMethod() { print(\"This is a class method.\") }}// 调用类方法MyClass.classMethod()```在这个示例中,`MyClass` 是一个类,它有一个类方法 `classMethod()`。`MyClass` 的类对象存储在堆栈中,而 `MyClass` 的元类存储在代码段中。**总结:*** 类对象存储在堆栈中* 元类存储在代码段中希望这些信息对您有所帮助。
元类存储在哪里
## Swift 方法查找过程**Swift 方法查找**是指在类或对象中找到要调用的方法的过程。方法查找的机制是**动态派发**,这意味着在运行时根据对象的类型确定要调用的方法。**方法查找过程**方法查找过程可以分为以下几个步骤:**1. 编译器查找*** 编译器会根据方法名查找类或对象的**方法表**。方法表是一个指针数组,每个元素指向一个方法的地址。* 如果方法名在方法表中找到,则编译器会将该方法的地址放入**指令**中。**2. 运行时查找*** 在运行时,CPU 会根据指令中的地址执行方法。* 如果方法是**实例方法**,则 CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。* 如果方法是**类方法**,则 CPU 会直接跳转到方法的地址。**方法查找的细节**方法查找的细节如下:* **方法表:**方法表存储在类的**元类**中。元类是类的类,它也是一个类对象。元类包含类的元数据,例如类的类型信息、类方法、类属性等。* **指令:**指令是 CPU 执行的代码。指令包含要调用的方法的地址。**方法查找的示例**以下是一个方法查找的示例:```swiftclass MyClass { func method() { print(\"This is a method in MyClass.\") }}let instance = MyClass()instance.method()```在这个示例中,`MyClass` 有一個方法 `method()`。`MyClass` 的方法表存储在 `MyClass` 的元类中。**编译器查找阶段*** 编译器会根据方法名 `method()` 查找 `MyClass` 的方法表,并找到方法 `method()` 的地址。* 编译器将该地址放入指令中。**运行时查找阶段*** 在运行时,CPU 会根据指令中的地址执行方法 `method()`。* 由于 `method()` 是实例方法,CPU 会将对象的地址压入堆栈,然后跳转到方法的地址。**方法重载****方法重载**是指同一个类中可以定义多个具有相同名称但参数不同的方法。在方法重载的情况下,方法查找过程会根据参数列表来确定要调用的方法。**方法重载的示例**以下是一个方法重载的示例:```swiftclass MyClass { func method(x: Int) { print(\"This is method with one parameter.\
方法查找的过程
问与答
枚举、结构体、类都可以定义方法
值类型和引用类型
06、结构体和类
在swift中,可以通过func定义一个函数,也可以用过闭包表达式定义一个函数
简写
闭包表达式(Closure Expression)
如果将一个很长的闭包表达式作为函数的最后一个实参,使用尾随闭包可以增强函数的可读性
示例
尾随闭包是一个被书写在函数调用括号外面的闭包表达式
是的
是逃逸闭包吗
数组排序的示例
忽略参数
如果闭包表达式是函数的唯一实参,而且使用了尾随闭包的语法,那就不需要在函数名后边写圆括号
尾随闭包
一个函数和它所捕获的常量、变量环境组合起来,称为闭包
定义
一般指定义在函数内部的函数
一般它捕获的是最外层函数的局部变量和常量
特点
内存在堆空间
捕获的局部变量和常量就是对象的成员,即存储属性
组成闭包的函数就是类内部定义的方法
可以把闭包想想成一个类的实例对象
理解
作为参数
方法内部函数作为返回值被返回(使用的闭包类型)
身为函数类型作为函数返回值
作为返回值
用来做什么
当我们谈到自动闭包时,我们通常是在谈论一种延迟执行的闭包。这意味着闭包内部的代码并不会立即执行,而是在闭包被调用时才会执行。这对于某些情况下很有用,特别是当我们需要传递一个代码块给函数,但是不希望立即执行这个代码块的时候。在Swift中,我们可以使用 `@autoclosure` 属性来创建自动闭包。这个属性的作用是将传递给函数的表达式自动封装成一个闭包,而不需要显式地编写闭包语法。考虑下面这个示例:```swiftfunc evaluate(condition: @autoclosure () -> Bool) { if condition() { print(\"条件为真\") } else { print(\"条件为假\") }}```在这个示例中,`evaluate` 函数接受一个参数 `condition`,这个参数是一个自动闭包,它的返回类型是 `Bool`。当我们调用 `evaluate` 函数时,传递给它的参数会被自动封装成一个闭包。然后,这个闭包在函数内部被调用,从而实现了延迟执行。这里是一个使用示例:```swiftevaluate(condition: 2 > 1)```在这个调用中,表达式 `2 > 1` 被自动封装成了一个闭包,然后传递给了 `evaluate` 函数。当 `condition()` 被调用时,闭包内部的代码 `2 > 1` 才会执行,结果是 `true`,所以打印出了 \"条件为真\"。自动闭包通常在需要延迟执行代码块的场景中使用,比如在某些条件满足时才执行某些操作,或者在性能要求较高的情况下避免不必要的计算。
自动闭包
闭包(Closure)
07、闭包
类似于成员变量这个概念
存储在实例的内存中
结构体、类可以定义存储属性,枚举不可以定义存储属性
可以在初始化器里为存储属性设置初始值
也可以分配一个默认的属性值作为属性定义的一部分
swift规定在创建类或者结构体的实例时,必须为所有存储属性设置一个合适的初始值。
非延迟存储属性
使用lazy可以定义一个延迟存储属性
表达
在第一次用到属性的时候才会进行初始化
延迟初始化
let必须在实例初始化方法完成之前就拥有值
lazy属性必须是var,不能是let
不可以设置属性观察器
如果多条线程同时第一次访问lazy属性时,无法保证属性只被初始化1次
坑
因为延迟属性初始化时需要改变结构体的内存
为什么
当结构体包含一个延迟存储属性时,只有var才能访问延迟存储属性
注意点
延迟存储属性
存储属性
本质就是方法
不占用实例的内存
枚举、结构体、类都可以定义计算属性
set传入新值默认叫做newValue,也可以自定义
只读属性只有get,没有set
计算属性的值是可能发生变化,所以不可以使用let修饰
定义计算属性智能用var,不能用let
枚举原始值rawValue的本质是:只读计算属性
枚举rawValue原理
计算属性
按照是否开辟空间用于存储分类
只能通过实例去访问
存储在实例内存中,每个实例都有一份
存储实例属性
计算实例属性
分为
实例属性
只能通过类型去访问
整个程序运行过程中,就只有一份内存(类似全局变量)
存储类型属性
计算类型属性
可以通过static定义类型属性
如果是类,也可以用关键字class
因为类型没有相实例那样的init初始化器来初始化存储属性
不同于实例属性,你必须给存储类型属性设置初始值
为什么?
存储类型属性默认就是lazy。在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次。
储存类型属性可以是let
枚举、结构体、类均可定义类型属性
细节
类型属性
按照实例和类型属性进行分类
willSet会传递新值,默认叫newValue
didSet会传递旧值,默认叫oldValue
在初始化器中设置属性值不会触发willSet和didSet
在属性定义时设置初始化值也不会触发willSet和didSet
这个威武这个帅呆了
属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上
全局变量和局部变量
会触发属性观察器
总结:inout的本质就是引用传递(地址传递)
如果实参有物理内存地址,且没有设置属性观察器,直接将示例的内存地址传入函数(实参进行引用传递)
调用函数时,先复制实参的值,产生副本【get】
将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
函数返回后,再将副本的值覆盖实参的值【set】
疑问,todo:产生了副本是什么意思,开辟新的空间了吗?
如果实参是计算属性或者设置了属性观察器,采取了copy In copy out的做法
inout的再次研究
可以直接设置属性观察器,不过如果是lazy的var存储属性不可以
实例计算属性间接设置属性观察器。操作静态和类属性同时添加属性观察器。在Swift中,类属性(包括静态属性和类属性)是不能直接设置属性观察器的。属性观察器(`willSet` 和 `didSet`)只能用于实例属性。如果你想要在类属性的值发生改变时执行某些操作,你可以使用计算属性和存储属性的组合,利用其中一个来存储值,另一个来提供属性观察器的功能。例如,你可以使用类方法来修改类属性,并在类方法中实现属性观察器的功能。下面是一个示例,演示了如何通过类方法修改类属性,并在类方法中实现属性观察器的功能:```swiftclass MyClass { private static var _myClassProperty: Int = 0 static var myClassProperty: Int { get { return _myClassProperty } set { // 在设置新值之前执行 willSet 观察器的功能 willSet { print(\"Will set new value for myClassProperty: \\(newValue)\") } _myClassProperty = newValue // 在设置新值之后执行 didSet 观察器的功能 didSet { print(\"Did set new value for myClassProperty: \\(myClassProperty)\") } } } static func updateClassProperty(newValue: Int) { myClassProperty = newValue }}MyClass.updateClassProperty(newValue: 10)// 输出:// Will set new value for myClassProperty: 10// Did set new value for myClassProperty: 10```在上面的示例中,`MyClass` 类有一个私有的存储属性 `_myClassProperty` 用于存储类属性的值,然后通过 `myClassProperty` 计算属性来提供对外的访问接口,并在其中实现了属性观察器的功能。同时,类方法 `updateClassProperty` 用于修改类属性的值。
不可以直接设置属性观察器,那我们如果想观察属性的改变怎么办呢?静态或类计算属性间接设置属性观察器。
类属性、静态属性不可以直接设置属性观察器
类属性、静态属性
属性都可以设置观察器吗?不是的
属性观察器(property Observer)
swift中实例相关的属性可以分为两大类
08、属性
结论:记忆:- 枚举:实- 结构体:实、静- 类:实、静、类- 枚举:只能定义实例方法。- 结构体:可以定义实例方法、静态方法。- 类:可以定义实例方法、静态方法、类型方法(类方法)。在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:### 枚举(Enumerations)枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。### 结构体(Structures)结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。### 类(Classes)类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。- **实例方法**:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。- **类型方法(类方法)**:使用 `class` 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。- **静态方法**:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 `static` 关键字代替 `class` 关键字来定义类型方法。它们的行为和用途与类型方法相同。需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
结论: 记:枚举、结构、类,321实静类记忆 枚举:实 结构体:实、静 类:实、静、类枚举只能定义实例方法。结构体可以定义实例方法和静态方法。类可以定义实例方法、类型方法(类方法)和静态方法。在 Swift 中,枚举(Enumerations)、结构体(Structures)和类(Classes)的方法定义有其特定的规则和限制。以下是关于它们如何定义实例方法、类型方法(也称为类方法)和静态方法的概述:枚举(Enumerations)枚举在 Swift 中可以定义关联值和方法。但是,枚举只能定义实例方法,不能定义类型方法或静态方法。这是因为枚举实例化的每个值都是枚举的一个独特实例,所以方法应该与特定实例关联。结构体(Structures)结构体可以定义实例方法和静态方法。由于结构体是值类型,所以每个结构体实例都有自己独立的拷贝和关联的方法。结构体不支持类型方法(类方法),因为类型方法通常与类(引用类型)的共享状态和行为相关联。类(Classes)类是 Swift 中的引用类型,它们可以定义实例方法、类型方法(类方法)和静态方法。实例方法:与类的特定实例相关联的方法。它们通过实例来调用,并且可以访问和修改实例的属性。类型方法(类方法):使用 class 关键字定义的方法,它们与类本身相关联,而不是类的特定实例。类型方法通过类名来调用,通常用于执行与类本身相关的操作,而不是与特定实例相关的操作。静态方法:在 Swift 中,静态方法实际上是类型方法的同义词。你可以使用 static 关键字代替 class 关键字来定义类型方法。它们的行为和用途与类型方法相同。需要注意的是,虽然结构体和类都支持静态方法,但由于结构体是值类型,静态方法通常用于执行与结构体类型本身相关的操作,而不是修改结构体实例的状态。而类的静态方法则可能用于执行与类相关的全局操作或提供工具函数。
枚举、结构体、类都可以定义实例方法、类型方法、静态方法吗
通过实例对象调用
实例方法
通过类型调用,用class关键字定义
类型方法
通过类型调用,用static关键字定义
静态方法
在 Swift 中,静态方法和类方法都与类本身相关联,而不是与类的特定实例相关联。尽管它们有相似之处,但它们在用法和特性上存在一些区别。### 静态方法(Static Methods)静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 `static` 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。### 类方法(Type Methods)类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 `class` 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。### 静态方法和类方法的区别1. **重写能力**:类方法可以被子类重写,而静态方法则不能被重写。2. **语义差异**:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。### 为什么要有静态方法静态方法的存在有几个原因:1. **方便性**:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。2. **组织代码**:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。3. **性能优化**:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
静态方法不能被继承,不可被重载在 Swift 中,静态方法和类方法都与类本身相关联,而不是与类的特定实例相关联。尽管它们有相似之处,但它们在用法和特性上存在一些区别。静态方法(Static Methods)静态方法是与类本身相关联的方法,它们可以通过类名直接调用,而不需要创建类的实例。静态方法主要用于执行与类本身相关的操作,而不是与特定实例相关的操作。在 Swift 中,静态方法实际上是类型方法的同义词,可以使用 static 关键字来定义。静态方法不能被重写,这意味着如果你在子类中定义一个与父类中静态方法同名的方法,那么子类的方法将隐藏父类的静态方法,而不是重写它。类方法(Type Methods)类方法也是与类本身相关联的方法,它们也使用类名来调用。在 Swift 中,类方法使用 class 关键字来定义。类方法的一个关键特性是它们可以被重写。这意味着子类可以提供一个与父类类方法同名的新实现,以改变方法的行为。静态方法和类方法的区别重写能力:类方法可以被子类重写,而静态方法则不能被重写。语义差异:虽然静态方法和类方法在功能上相似,但它们的语义略有不同。静态方法更强调其静态性质,即它们不依赖于类的任何特定实例。类方法则更强调它们与类本身的关联,同时提供了重写的灵活性。为什么要有静态方法静态方法的存在有几个原因:方便性:静态方法允许你通过类名直接调用方法,而无需创建类的实例。这在某些情况下非常方便,特别是当你需要执行与类本身相关但不涉及实例状态的操作时。组织代码:静态方法可以用于组织那些与类本身相关但不依赖于实例状态的功能。这有助于将代码划分为更小的、更易于管理的部分。性能优化:静态方法通常使用静态派发,这意味着编译器在编译时就能确定调用哪个方法,避免了运行时的查找和解析过程。这可以提高代码的性能。总的来说,静态方法和类方法都是与类本身相关联的方法,它们在用法和特性上有所不同。静态方法强调其静态性质,而类方法则提供了重写的灵活性。选择使用哪种方法取决于具体的需求和场景。
swift 的静态方法和类方法有什么区别 为什么要有静态方法呢?
思
在类型方法static func getCount中,count等价于self.count、Car.self.count、Car.cout
在实例方法中代表实例对象自身
在类型方法中代表类型自身
self
区别
在func关键字前面加mutating(突变)可以允许这种修改行为
如何解决呢
结构体和枚举是值类型,默认情况下值类型的属性不能被自身的实例方法修改。
关键字mutating(突变)
在func前面加个@discardable,可以消除:函数调用后返回值未被使用的警告⚠️
关键字@discardableResult
// 示例:struct Person { var age: Int func run(_ v: Int) { print(\"func run\
方法也可以像函数那样,赋值给一个let或者var
将方法赋值给var、let
09、方法
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的语法类似于实例方法、计算属性,本质就是方法
可以没有set方法,但必须要有get方法
如果只有get方法可以省略get
可以设置参数标签
下标可以是类型方法
结构体、类作为返回值对比
接收多个参数的下标
10、下标(subscript)
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,称为基类
swift并没有像OC、java那样的规定:任何类最终都要继承自某个基类
子类可以重写父类的下标、方法、属性,重写必须加上override关键字
看视频
实例方法和下标
被class修饰的类型方法、下标,允许被子类重写
被static修饰的类型方法、下标,不允许被子类重写
类型方法和下标
子类可以将父类的属性(存储、计算)重写为计算属性
子类不可以将父类的计算属性重写为存储属性
只能重写var属性,不能重写let属性
重写时,属性名、类型要一致
如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
如果父类属性是可读的,那么子类重写后的属性也必须是可读写的
子类重写后的属性权限,不小于父类属性的权限
重写规则
被class修饰的属性(存储和计算),可以被子类重写。
被static修饰的属性(存储、计算),不可以被子类重写
可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
具有继承属性
结论:子类重写了父类的属性,修改子类的该属性,父类的属性的观察器不会触发。如果想让父类的观察器触发,需要在子类重写属性是手动调用父类的触发器。在 Swift 中,如果你重写了父类的属性,并且父类属性的观察器(property observer,比如 willSet 和 didSet)存在,那么这些观察器不会自动在子类属性的值改变时被触发。子类重写了父类的属性,实际上是在子类层面创建了一个新的属性。这个新的属性与父类的属性是完全独立的,除非你明确地调用父类的属性或在其 willSet 或 didSet 观察器中手动调用它们。以下是一个例子来阐述这一点:swift复制代码class ParentClass { var someProperty: Int = 0 { willSet(newValue) { print(\"ParentClass someProperty will be set to \\(newValue)\") } didSet { print(\"ParentClass someProperty was set to \\(someProperty)\") } } } class ChildClass: ParentClass { override var someProperty: Int { get { return super.someProperty } set { super.someProperty = newValue } } } let child = ChildClass() child.someProperty = 10 // 这里不会触发 ParentClass 的观察器在上面的例子中,ChildClass 重写了 someProperty 属性,并且在其 getter 和 setter 中调用了 super 来访问和设置父类的 someProperty。但是,当我们改变 ChildClass 实例的 someProperty 属性时,ParentClass 中的 willSet 和 didSet 观察器并不会被触发。如果你希望父类的观察器在子类属性改变时也被触发,你需要在子类的 setter 中显式地调用父类的 setter,或者手动触发父类观察器中的代码。例如:swift复制代码class ChildClass: ParentClass { override var someProperty: Int { get { return super.someProperty } set { willSet { newValue in // 这里可以手动调用父类的 willSet super.someProperty.willSet(newValue) } didSet { // 这里可以手动调用父类的 didSet super.someProperty.didSet } super.someProperty = newValue } } }在这个修改后的 ChildClass 中,我们在子类的 setter 中手动调用了父类的 willSet 和 didSet 观察器。这样,当子类的 someProperty 被设置时,父类的观察器也会被触发。但是请注意,这样做可能会引入额外的复杂性和潜在的副作用,因此在实际编程中应谨慎使用。
子类重写了父类的属性,父类的属性观察器还可以被触发吗
属性观察器
被final修饰的方法、下标、属性禁止被重写
被final修饰的类,禁止被重写
final
属性
final、static 属性不可被重写。父类属性有观察器,子类实例该属性改变时,会触发父类属性观察器。如果子类重写了父类的该属性,子类就不会触发,只有重写父类该属性时手动调用才可以触发父类的属性观察器。
重写
11、继承(Inheritance)
类、结构体、枚举都可以定义初始化器
指定初始化器(designated initializer)
便捷初始化器(convenience initializer)
类有2种初始化器
每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
默认初始化器总是类的指定初始化器
类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
指定初始化器必须从它的直系父类调用指定初始化器
便捷初始化器必须从相同的类里调用另一个初始化器
便捷初始化器最终必须调用一个指定初始化器
图示
为了保证使用任意初始化器,都可以完整地初始化实例
为什么有这样的规则
初始化器的相互调用规则
外层调用指定、便捷初始化器
分配内存给实例,但未初始化
指定初始化器确保当前类定义的存储属性都初始化
指定初始化器调用父类的初始化器,不断向上调用,形成初始化链
初始化所有存储属性
第一阶段
从顶部初始化器往下,链中的每一个指定初始化器都要机会进一步初始化定制实例
初始化器现在能够使用self(访问、修改它的属性,调用跳的实例方法等等)
最终链中任何便捷初始化器都有机会定制实例以及使用self
设置新的存储属性值
第二阶段
两段式初始化(初始化链)(嵌套结构)
指定初始化器必须保证在调用父类初始化器之前,其所在类定义的所有存储属性都要初始化完成
指定初始化器必须先调用父类初始化器,然后才能为继承属性设置新值
便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
助记:self、实例属性、实例方法
初始化器在第一节吨初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
直到第一阶段完成,实例才算合法
安全检查
助记:重写父类的指定初始化器必须加override
当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)
助记:重写父类的便捷初始化器不用加override
额这个 那为什么还要说这么多呢。。。。不可能的事情就这样吧
因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器
如果子类写了一个匹配父类便捷初始化器的初始化器,不用加上override
如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器
子类自动继承所有父类的便捷初始化器
如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写)
就算子类添加了更多的便捷初始化器,这些规则依然适用
子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分
自继承
用required修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
如果子类重写了required初始化器,也必须加上required,不用加override
required关键字
父类属性在它自己的初始化器中赋值不会出发属性观察器,但在子类的初始化器中赋值会触发属性观察器
类结构体、枚举都可以使用init?定义可失败初始化器
可以用init!定义隐式解包的可失败初始化器
不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器
如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的
可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
可失败初始化器
deinit叫做反初始化器,类似c++的析构函数,oc中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
dinit不接受任何参数,不能写小括号,不能自行调用
父类deinit能被子类继承
子类的deinit实现执行完毕后会调用父类的deinit
反初始化器
swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、安全检查
为什么要有两段式初始化和安全检查呢?
12、初始化
多个?可以链接在一起构成了可选链
如果可选项为空,调用方法、下标、属性失败,结果为nil
如果可选项不为空,调用方法、下标、属性成功,结果会被包装成可选项,如果结果本来就是可选项,不会进行再次包装
如果链中任何一个节点是nil,那么整个链就会调用失败
满足如上第三个规则
var num1: Int? = 5num1? = 10 // Optional(10)var num2: Int? = nilnum2? = 10 // nil
13、可选链(Optional Chaining)
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守
实现多个协议,多个协议之间用逗号隔开
协议中定义方法时不能有默认值参数
默认情况下,协议中定义的内容必须全部都实现
可选协议方法使用 @optional 关键字标记,表示该方法的实现可选。
也有办法办到只实现部分内容
协议中定义属性时必须用var关键字
因为有set
协议定义get、set,只能用var存储属性或get、set计算属性去实现
todo 不明白不明白
协议定义get,用任何属性都可以实现
实现协议时的属性权限要不小于协议重定义的属性的权限
不可以,在 Swift 协议中**不能**使用 `class` 关键字定义属性或者方法。协议只定义了类、结构体和枚举**必须实现**的功能,而 `class` 关键字通常用于定义类属性和类方法。协议中只能使用 `static` 关键字定义类型属性和类方法。以下是有关在 Swift 协议中定义属性和方法的规则:* **属性** * 只能使用 `var` 关键字定义属性,不能使用 `let` 关键字。 * 类型属性必须使用 `static` 关键字作为前缀。 * 必须声明属性是可读的还是可读可写的。* **方法** * 实例方法和类方法没有方法体,允许可变参数,但是不能为协议中的方法参数指定默认值。 * 类方法必须使用 `static` 关键字作为前缀。以下是一个示例,演示了如何在 Swift 协议中定义属性和方法:```swiftprotocol Drawable { static var title: String { get } // 类型属性 var color: String { get set } // 实例属性 func draw() // 实例方法 static func create(withColor color: String) -> Self // 类方法}```请注意,协议中不能定义构造方法。如果需要在协议中定义构造方法,可以使用 `required` 关键字标记该方法,并将其放在遵守该协议的类、结构体或枚举中实现。希望这些信息对您有所帮助。
swift 协议中可以使用class 关键字定义属性、方法、下标吗?不可以,在 Swift 协议中不能使用 class 关键字定义属性、方法、下标。
类型属性、类型方法、类型下标
可以使用static关键字定义静态属性、方法、下标
静态属性、静态方法、静态下标
只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身的内存
作用
类在实现方法时不用加mutating,枚举和结构体才需要加mutating
突变方法:(mutating)
协议中还可以定义初始化器init
非final类实现时必须加上required
如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化器必须同时加上required和override
可以用init、init?、init!实现
协议中定义的init?、init!
协议中定义的init,可以用init、init!去实现
init 初始化器
协议中的
单继承
问题:协议继承是单继承还是多继承?
一个协议可以继承多个其他协议
协议的继承
问所未闻,令人惊叹
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例func fn3(obj: Person & Livable & Runnable) {}扩展:// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例 typealias RealPerson = Person & Livable & Runnablefunc fn4(obj: RealPerson) {}
协议组合中可以包含无数个协议
协议组合中最多可以包含一个类类型
协议组合&:协议 & 协议 & 类
闻所未闻,闻所未闻
让枚举遵守CaseIterable协议,可以实现遍历枚举值
遍历枚举
应用
caseiterable协议
遵守CustomStringConvertible、CustormDebugStringConvertible协议,都可以自定义实例的打印字符串
print调用的事CustomStringConvertible协议的description
debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription
自定义实例打印字符串
customStringConvertible协议 和 customDebugStringConvertible协议
内置协议
swift提供了两种特殊的类型:Any、AnyObject
可以代表任意类型,如枚举、结构体、类,也包含函数类型
Any
在协议后面写上:AnyObject代表只有类能遵守这个协议
在协议后面写:class也代表只有类能遵守这个协议
可以代表任意类类型
AnyObject
Any、AnyObject类型
is用来判断是否是某种类型
as永磊做强制类型转换
is、as?、as!、as的用法
类型判断与类型转换
用来描述类的类型
什么是元类型
多态
应用1
ClassX.self的类型
ClassX.Type
是一个元类型(metadata)的指针,metadata存放着类型相关信息
属于ClassX.Type类型
示例1
示例2
ClassX是一个类
ClassX.self
ClassX.self、ClassX.Type
Swift._SwiftObject
https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
参考源码
swift隐藏的基类
元类、元类型
self 代表当前的对象
Self一般用作返回值类型,限定返回值跟方法调用者必须是统一类型,也可以作为参数类型
Self代表当前的类型
self与Self
在 Swift 中,`AnyClass` 是一个**类型别名**,代表**任何类的类型**。它本质上是一个包装类型,用于表示可以转换为任何类实例的值。`AnyClass` 与 `Any` 类型密切相关,但两者之间存在细微差别:* **`Any` 类型**可以表示**任何值类型**,包括类实例、结构体实例、枚举值、函数、元组等。* **`AnyClass` 类型**只可以表示**类实例**。换句话说,`Any` 类型的范围更广,可以表示任何值类型,而 `AnyClass` 类型的范围更窄,只可以表示类实例。`AnyClass` 类型通常用于以下场景:* **动态分派**:当您需要在不知道具体类型的情况下调用某个方法或属性时,可以使用 `AnyClass` 类型。例如,以下代码使用 `AnyClass` 类型来调用 `description` 方法:```swiftlet object: AnyClass = Person()let description = object.description // 输出:Person```* **类型检查**:您可以使用 `is` 运算符来检查某个值是否为 `AnyClass` 类型。例如,以下代码检查 `object` 是否为 `Person` 类实例:```swiftlet object: AnyClass = Person()if object is Person { let person = object as! Person // 强制转换类型 print(person.name) // 输出:John Doe}```* **泛型编程**:在泛型代码中,您可以使用 `AnyClass` 类型作为参数或返回值,以支持任何类类型。例如,以下泛型函数可以接受任意类型的可迭代集合,并返回该集合中所有元素的描述:```swiftfunc describe<T: AnyClass>(collection: [T]) -> [String] { return collection.map { $0.description }}```总而言之,`AnyClass` 类型是一种特殊类型,用于表示任何类的类型。它在动态分派、类型检查和泛型编程等场景中非常有用。希望这些信息对您有所帮助。如果您还有其他问题,请随时提出。
Any、AnyClass
14、协议(Protocol)
编译期间报错
语法错误
逻辑错误
可能会导致闪退,一般也叫做异常
swift中可以通过Error协议自定义运行时的错误信息
函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
需要使用try调用可能会抛出Error的函数
自定义运行时错误
可以使用do-catch捕捉Error,抛出Error后,try下一句直到作用域结束的代码都将停止运行
do-catch
通过do-catch捕捉Error
如果顶层函数(main函数)依然没有剥啄Error,那么程序将终止
不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数
处理Error的两种方法
可以使用try!、try?调用可能会抛出Error的函数,这样就不会去处理Error
是try-catch的语法糖
本质是什么
强制解包?或者称之为隐式解包
有什么区别
try?、try!
函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
rethrows
用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
defer语句将延迟至当前作用域结束之前执行
defer语句的执行顺序与定义顺序相反
defer
很多编程语言都有断言机制
不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
默认情况下,swift断言只会在Debug模式下生效,Release模式下会忽略
强制关闭断言
-assert-config Release
强制开启断言
-assert-config Debug
增加Swift Flags修改断言的默认行为
assert(断言)
如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch剥啄的错误)
使用fatalError函数,就不需要写return
在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用fatalError函数
fatalError
可以使用do实现局部作用域
局部作用域
运行时错误
开发过程常见的错误
15、错误处理
类型参数化,提高代码复用率,减少代码量
泛型函数赋值给变量
// 泛型和类一起使用:实现栈class Stack<E> { var elements = [E]() func push(_ element: E) { elements.append(element) } func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count }}class SubStack<E> : Stack<E> {}var stack = Stack<Int>()stack.push(11)stack.push(22)stack.push(33)print(stack.top()) // 33print(stack.pop()) // 33print(stack.pop()) // 22print(stack.pop()) // 11print(stack.size()) // 0
// 泛型和结构体一起使用:实现栈struct Stack<E> {var elements = [E]()mutating func push(_ element: E) { elements.append(element) } mutating func pop() -> E { elements.removeLast() }func top() -> E { elements.last! }func size() -> Int { elements.count }}
// 泛型和枚举一起使用enum Score<T> { case point(T) case grade(String)}let score0 = Score<Int>.point(100) let score1 = Score.point(99)let score2 = Score.point(99.5)let score3 = Score<Int>.grade(\"A\")
协议:不可以,那怎么办呢,swift为我们推出了关联类型解决,协议无法使用泛型的问题
泛型可以和那些类型结合使用呢?
protocol Stackable { associatedtype Element // 关联类型 mutating func push(_ element: Element) mutating func pop() -> Element func top() -> Element func size() -> Int}// 协议、关联类型、泛型 一起使用示例class Stack<E> : Stackable { // 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断 typealias Element = E var elements = [E]() func push(_ element: E) { elements.append(element) } func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count }} // 普通类型关联类型使用示例class StringStack : Stackable { // 给关联类型设定真实类型,注释掉下一行仍然可以正常运行,系统会进行类型推断 typealias Element = String var elements = [String]() func push(_ element: String) { elements.append(element) } func pop() -> String { elements.removeLast() } func top() -> String { elements.last! } func size() -> Int { elements.count } }var ss = StringStack()ss.push(\"Jack\")ss.push(\"Rose\")//注释显示的关联类型,仍然可以运行,说明系统会自动推断关联类型。此处的类型推断有点类似声明变量的时候的类型推断
如何使用呢,示例
作用:关联类型的引入解决了协议中无法使用泛型的限制,使其能够更加灵活地定义通用接口。
特性:协议中可以拥有多个关联类型可以显示为关联类型赋值,也可以不赋值,由系统自动进行类型推断
关联类型(associated Type)
是什么:Swift 中的泛型类型约束是一种用于指定泛型类型参数必须满足的条件的机制。有什么作用:有助于确保泛型代码的类型安全性并使其更具鲁棒性。
泛型类型约束
错误
1、使用泛型
some限制只能返回一种类型
2、使用some关键字声明一个不透明类型
解决方案
不理解,看视频
限制只能返回一种类型
可以用在返回值类型前面
也可以用在属性类型前面
作用和协议搭配使用
some
没看懂,没看懂,看视频,看视频
可选项的本质是enum类型
可选项本质
如果协议中有关联类型
协议类型的注意点
总结:协议、关联类型一起使用构建了一个灵活多变的复用体
16、泛型(Generics)
swift语言的内存结构:记忆:可Mach-O、代常全 堆栈动
总结:短字符串:栈上,最多 15 个字符,15个字符+1个终止符;较长字符串(超过 15 个字符):栈+堆,前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。## Swift 中 String 变量的内存占用在 Swift 中,`String` 变量的内存占用取决于字符串的长度以及它是存储在堆上还是栈上。**短字符串(最多 15 个字符)**对于短字符串(最多 15 个字符),字符串值直接存储在变量的内存空间中。这意味着变量占用 16 个字节的内存,而不管字符串的实际长度是多少。额外的 1 个字节用于空终止符 (`\\0`)。**较长字符串(超过 15 个字符)**对于较长字符串(超过 15 个字符),字符串值存储在堆上,变量只存储对堆内存位置的引用。这意味着变量本身只占用 16 个字节的内存,但实际的字符串数据在堆上占用额外的内存。**String 变量的内存布局**`String` 变量的内存布局可以如下说明:```| 8 bytes | 8 bytes ||------------------------|------------------------|| String reference | String length and flags ||------------------------|------------------------|```前 8 个字节存储对堆上字符串数据的引用。接下来的 8 个字节存储字符串长度和标志。标志指示字符串是否存储在堆上或栈上,以及有关字符串的其他信息。**影响内存使用的因素**`String` 变量的内存使用量可能会受到以下几个因素的影响:* **字符串长度:** 较长的字符串将比较短的字符串消耗更多的内存。* **字符串编码:** 字符串的编码(例如,UTF-8、UTF-16)也会影响内存使用量。* **字符串不可变性:** 不可变字符串(用 `let` 声明)通常比可变字符串(用 `var` 声明)更具内存效率。**优化内存使用**为了优化字符串的内存使用,您可以考虑以下做法:* **尽可能使用短字符串:** 如果您只需要存储一段短文本,请使用短字符串来避免不必要的内存分配。* **使用适当的字符串编码:** 根据您的需要选择适当的字符串编码。例如,如果您只存储 ASCII 字符,请使用 UTF-8 编码。* **尽可能使用不可变字符串:** 尽可能将字符串声明为不可变(`let`),以避免不必要的复制。总的来说,Swift 的内存管理系统非常高效,并且会自动处理内存分配和释放。但是,了解影响内存使用量的因素可以帮助您编写更具内存效率的代码。
1个String变量占用多少内存?
var str1 = \"0123456789\"
var str2 = \"0123456789ABCDEF\"
下面2个String变量,底层存储有什么不同?
如果对String进行拼接操作, String变量的存储会发生什么变化?var str1 = \"0123456789\"var str2 = \"0123456789ABCDEF\" str1.append(\"ABCDE\")str1.append(\"F\")str2.append(\"G\")
如果对String进行拼接操作,String变量的存储会发生什么变化?
关于String的思考
从编码到启动APP
符号的延迟绑定通过dyld_stub_binder完成
jmpq *0xb31(%rip)格式的汇编指令
占用6个字节
dyld_stub_binder(符号延迟绑定)
1个Array变量占用多少内存?
数组中的数据存放在哪里?
关于Array的思考
17、String与Array
Swift的算术运算符出现溢出时会抛出运行时错误
swift有溢出运算符(&+、&-、&*),用来支持溢出运算
255 - (x-1)
&-
(x-1)
&+
&*
溢出运算符(overflow Operator)
类、结构体、枚举可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载
运算符重载(Operator Overload)
判断两个实例是否等价,一般做法是遵循Equatable协议,重载==运算,或者重载!=运算符
应用:自定义相等运算符
没有关联类型的枚举
只拥有遵守Equatable协议关联类型的枚举
只拥有遵守Equatable协议存储属性的结构体
swift为一下类型提供默认的Equatable实现
引用类型比较存储的地址值是否相等(是否引用着同一个对象,使用恒等运算符===,!==)
Equatable协议:自定义两个实例相等策略
遵守Comparable协议
重载相应的运算符(<、>、<=、>=、)
想要比较2个实例的大小,一般做法是:
Comparable协议:自定义两个实例比较策略
比较运算符相关的内置协议
在全局作用域使用operator进行声明
前缀运算符
prefix operator
后缀运算符
postfix operator
中缀运算符:优先级组
infix operator 默认
声明用的关键字
precedencegroup 优先级组 {associativity: 结合性(left\ight\one)higherThan: 比谁的优先级高lowerThan: 比谁的优先级低assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级}
优先级
Apple文档参考: https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations
https://docs.swift.org/swift- book/ReferenceManual/Declarations.html#ID380
https://docs.swift.org/swift- book/ReferenceManual/Declarations.html#ID380
参考
prefix operator +++infix operator +- : PlusMinusPrecedence precedencegroup PlusMinusPrecedence { associativity: none higherThan: AdditionPrecedence lowerThan: MultiplicationPrecedence assignment: true}
如何自定义新的运算符
自定义运算符(Custom Operator)
18、高级运算符
swift中的扩展,有点类似于OC中的分类(Category)
目的是为了复用,有了继承为什么还要引入扩展呢,非侵入复用扩展
extension Double { var km: Double { self * 1000.0 } var m: Double { self } var dm: Double { self / 10.0 } var cm: Double { self / 100.0 } var mm: Double { self / 1000.0 }}
extension Array { subscript(nullable idx: Int) -> Element? { if (startIndex..<endIndex).contains(idx) { return self[idx] } return nil }}
下标
扩展可以给协议提供默认实现,也可以简介实现“可选协议”的效果
扩展可以给协议扩充“协议中从未声明过的方法”
特点:协议扩展独有方法 > 实例方法 协议扩展独有方法 > 实例方法 > 协议和协议扩展都有的方法 > 父类的方法protocol TestProtocol { func test1()}extension TestProtocol { func test1() { print(\"TestProtocol test1\") } func test2() { print(\"TestProtocol test2\") }}class TestClass : TestProtocol { func test1() { print(\"TestClass test1\") } func test2() { print(\"TestClass test2\") }}var cls = TestClass()cls.test1() // 打印 TestClass test1 cls.test2() // 打印 TestClass test2var cls2: TestProtocol = TestClass()cls2.test1() // 打印 TestClass test1 cls2.test2() // 打印 TestProtocol test2 extension 协议的扩展的特殊性,为什么会这样❓运行结果:TestClass test1TestClass test2TestClass test1TestProtocol test2 extension
协议
初始化器
泛型
可以添加方法、计算属性、下标、便捷初始化器、嵌套类型、协议等等
扩展可以为枚举、结构体、类、协议添加新功能
不能覆盖原有功能
不能添加存储属性,不能向已有的属性添加属性观察器
不能添加父类
不能添加指定初始化器
不能添加反初始化器
required初始化器也不能写在扩展中
扩展不能办到的事情
可以在扩展中编写初始化器
如果希望自定义初始化器的同时,编译器也能够生成默认初始化器
protocol TestProtocol { func test()}class TestClass { func test() { print(\"test\") }}extension TestClass : TestProtocol {}
如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来遵守并实现某个协议
func isOdd<T: BinaryInteger>(_ i: T) -> Bool { i % 2 != 0}extension BinaryInteger { func isOdd() -> Bool { self % 2 != 0 }}
编写一个函数,判断一个整数是否是奇数?
19、扩展(Extension)
private: 只能在定义属性的类或结构体中访问fileprivate: 只能在定义属性的源文件中访问internal: 只能在同一个模块内的类、结构体和枚举中访问,隐式为internal访问级别public: 在任何地方都可以访问open: 在任何地方都可以访问,并且可以被子类重写
实例属性的五个访问级别摘要
最大可见性规则
遵循最大访问级别规则
类属性
静态属性
类方法
类:所有成员均遵循最大可见性规则
所有成员均遵循最大可见性规则
不能给用于遵守协议的扩展显式设置扩展的访问级别
在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
在同一文件中的扩展,可以写成类似多个部分的类型声明
隐式遵循所属类型的的可见性规则,所有成员均遵循最大可见性规则
扩展
访问控制的传递性
变量、常量类型>=变量常量
参数类型、返回值类型>=函数
父类>=子类
父协议>=子类
原类型>=typealias
原始值类型、关联值类型>=枚举类型
定义类型A时用到的其他类型>=类型A
元组的访问级别为所有成员类型中级别最低的那个成员类型的访问级别
泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个泛型类型或者参数的访问级别
泛型类型
一般情况下,类型为private或fileprivate,那么成员、嵌套类型默认也是private或者fileprivate
一般情况下,类型为internal或者public,那么成员、嵌套类型默认是internal
类型的访问级别会影响成员(属性、方法、初始化器、下标)、嵌套类型的默认访问级别
成员、嵌套类型
子类重写成员的访问级别必须>=子类的访问级别,或者>=父类被重写成员的访问级别
父类的成员不能被成员作用域外定义的子类重写
成员的重写
在全局作用域下定义的private等价于fileprivate
默认自动接收他们所属环境的访问级别
可以给setter单独设置一个比getter更低的访问级别,用一限制写的权限
getter、setter
如果一个public类想在另外一个模块调用编译生成的默认无参初始化器,必须显示提供public的无参初始化器
因为public类的默认初始化器是internal级别
required初始化器》=它的默认访问级别
如果结构体有private、fileprivate的存储实例属性,那么它的成员初始化器也是private、fileprivate,否在默认是internal
不能给enum的每个case单独设置访问级别
每个case自动接收enum的访问级别
public enum定义的case也是public
枚举类型的case
协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
public协议定义的要求是public
协议实现的访问级别必须》=类型的访问级别,或者》=协议的访问级别
一个实体不可以被更低访问级别的实体定义,比如
访问控制的使用准则
其他先忽略
20、访问控制(Access Control)
跟oc一样,针对堆空间,swift也采取基于引用计数的ARC内存管理方案
默认情况下,引用都是强引用
强引用(strong reference)
必须是可选类型的var,因为实例销毁后,arc会自动将弱引用设置为nil
说明手动置空的时候会触发属性观察器
arc自动给弱引用设置nil时,不会触发属性观察器
通过weak定义弱引用
弱引用(weak reference)
通过unowned定义无主引用
不会产生强引用,实例销毁后仍然存储着实例的内存地址,类似oc中的unsafe_unretained
fatal error:attempted to read an unowned reference but object 0x0 was already deallocated
视图在实例销毁后访问无主引用,会产生运行时错误(野指针)
无主引用(unowned reference)
swift的arc中有三种引用
weak、 unowned 只能用在类实例上面protocol Livable: AnyObject {}class Person{}weak var p0: Person?weak var p1: AnyObject?weak var p2: Livable?unowned var p10: Person?unowned var p11: AnyObject?unowned var p12: Livable?
只能用在类实例上面,结构体和枚举不可以用
weak、unowned
Autoreleasepool
非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
闭包调用发生在函数结束前,闭包调用在函数作用域内
非逃逸闭包
闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
逃逸闭包
区分
import Dispatchtypealias Fn = () -> ()class Person { var fn: Fn// fn是逃逸闭包init(fn: @escaping Fn) {self.fn = fn }func run() {// DispatchQueue.global().async也是一个逃逸闭包// 它用到了实例成员(属性、方法),编译器会强制要求明确写出self DispatchQueue.global().async {self.fn() }} } // fn是非逃逸闭包func test1(_ fn: Fn) { fn() } // fn是逃逸闭包var gFn: Fn?func test2(_ fn: @escaping Fn) { gFn = fn } // fn是逃逸闭包func test3(_ fn: @escaping Fn) {DispatchQueue.global().async { fn()} }
typealias Fn = () -> ()func other1(_ fn: Fn) { fn() }func other2(_ fn: @escaping Fn) { fn() }func test(value: inout Int) -> Fn {other1 { value += 1 }// error: 逃逸闭包不能捕获inout参数 other2 { value += 1 }func plus() { value += 1 }// error: 逃逸闭包不能捕获inout参数 return plus}
不可以捕获inout参数
逃逸闭包 @escaping
使用weak、unowned都能解决循环引用的问题,unowned要比weak小一些性能消耗
在生命周期中可能会变为空的使用weak
初始化复制后再也不会变为空的使用unowned
两个都可以解决循环引用,何时使用weak,何时使用unowned呢?
如何解决
闭包表达式默认会对用到的外层对象产生额外的强引用,对外层对象进行了retain操作
class Person { var fn: (() -> ())? func run() { print(\"run\") } deinit{ print(\"deinit\
产生循环引用的示例
在闭包表达式的捕获列表声明 weak 或unowned 引用,解决循环引用问题
因为在实例初始化完毕之后才能引用self
为什么呢
如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的
// 示例class Person{ lazy var fn: (() -> ()) = { [weak self] in self?. run() } func run() { print(\"run\") } deinit { print(\"deinit\") }}
左边的闭包fn内部如果用到了实例成员(属性或者方法),编译器会强制要求明确写明self
class Person{ var age: Int = 0 lazy var getAge: Int = {self.age}() deinit{ print(\"deinit\") }}class Person{ var age: Int = 0 lazy var getAge: Int = {self.age}() deinit{ print(\"deinit\") }}
如果 lazy 属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生周期就结束了)
如上两个示例对比,一个是闭包属性,一个是以闭包的执行结果作为属性
闭包的循环引用
常见的循环引用
循环引用(Reference Cycle)
UnsafePointer<Pointee>类似于const Pointee*
UnsafeMutablePointer<Pointee>类似于Pointee*
UnsafeRawPointer类似于const void*
UnsafeMutableRawPointer类似于 void *
swift也有专门的指针类型,这些都被定性为“Unsafe”(不安全),常见的有以下4中类型
(遍历替代break)示例2
获得指向某个变量的指针
获得指向堆空间实例的指针
创建
直接存
移位存
增强指针存
存
直接取
移位如何取
增强指针如何取
取
普通的如何销毁
增强的如何销毁
销毁
后续再做了解
示例3
创建指针
类似于C++中的reinterpret_cast
unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
指针之间的转换
指针
至少一个是写入操作
它们访问的是同一块内存
它们的访问时间重叠(比如在同一个函数内)
内存访问冲突会在两个访问满足下列条件时发生:
你只访问实例存储属性,不是计算属性或者类属性
结构体是局部变量而非全局变量
结构体要么没有被闭包捕获要么只被非逃逸闭包捕获
如果下面的条件可以满足,就说明重叠访问结构体的属性时安全的
内存访问冲突(Conflicting Access to Memory)
21、内存管理
什么是字面量
ppublic typealias IntegerLiteralType = Int public typealias FloatLiteralType = Double public typealias BooleanLiteralType = Bool public typealias StringLiteralType = String
常见字面量的默认类型
Bool、Int、Float、Double、String、Array、Dictionary、Set、Optional等
swift自带的绝大部分类型,都支持直接通过字面量进行初始化
Bool : ExpressibleByBooleanLiteralInt : ExpressibleByIntegerLiteralFloat、Double : ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral p Dictionary : ExpressibleByDictionaryLiteralString : ExpressibleByStringLiteralArray、Set : ExpressibleByArrayLiteral p Optional : ExpressibleByNilLiteral
因为它们遵守了对应的协议
swift自带类型为什么可以通过字面量初始化?
字面量初始化
最佳实践
字面量协议
22、字面量(Literal)
模式是用于匹配的规则,比如switch的case、捕捉错误的catch、if、guard、while、for语句的条件等
什么是模式
_ 匹配任何值
_? 匹配非nil值
通配符模式(Wildcard Pattern)
var age = 10let name = \"jack\"
给对应的变量、常量名赋值
标识符模式(Identifier Pattern)
值绑定模式(Value-Binding Pattern)
元组模式(Tuple Pattern)
if case语句等价于只有1个case的switch语句
总结:除了switch case 之外还可以有 if case、guard case 、for case几乎所有的条件判断语句都可以结合 case 使用let age = 2// 原来的写法if age >= 0 && age <= 9 { print(\
枚举Case模式(Enumeration Case Pattern)
let age: Int? = 42if case .some(let x) = age { print(x) }if case let x? = age { print(x) }func check(_ num: Int?) { switch num { case 2?: print(\"2\") case 4?: print(\"4\") case 6?: print(\"6\") case _?: print(\"other\") case _: print(\"nil\
可选模式(Optional Pattern)
let num: Any = 6switch num {case is Int: // 编译器依然认为num是Any类型 print(\"is Int\
厉害了,厉害了
类型转换模式(Type-Casting Pattern)
表达式模式用在case中
表达式模式(Expression Pattern)
厉害了,通过重载student的不等于运算符,switch-case的直接比较
函数作为switch-case 表达式进行匹配
运算符重载,函数表达式匹配,运算符匹配
可以通过重载运算符,自定义表达式模式的匹配规则
自定义表达式模式
case关键字
可以使用where为模式匹配增加匹配条件
where关键字
swift中的模式有
23、匹配模式
// MARK: 类似于OC中的 #pragma mark// MARK: - 类似于OC中的 #pragma mark - // TODO: 用于标记未完成的任务// FIXME: 用于标记待修复的问题
编辑器指令
// 操作系统:macOS\\iOS\\tvOS\\watchOS\\Linux\\Android\\Windows\\FreeBSD #if os(macOS) || os(iOS)// CPU架构:i386\\x86_64\\arm\\arm64#elseif arch(x86_64) || arch(arm64)// swift版本#elseif swift(<5) && swift(>=3)// 模拟器#elseif targetEnvironment(simulator) // 可以导入某模块#elseif canImport(Foundation)#else#endif
预编译代码
打印
系统版本检测
API可用性说明
iOS程序的入口
1、新建1个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h2、在{targetName}-Bridging-Header.h 文件中#import OC需要暴露给Swift的内容 #import \"MJPerson.h\
如何调用(桥接)?
MJPerson.h
MJPerson.m
Swift代码
示例代码
可以在Swift中使用 @_silgen_name 修改C函数名
如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,我们如何解决
swift调用OC
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是: {targetName}-Swift.h
Swift暴露给OC的类最终继承自NSObject n 使用@objc修饰需要暴露给OC的成员使用@objcMembers修饰类 p代表默认所有成员都会暴露给OC(包括扩展中定义的成员) 最终是否成功暴露,还需要考虑成员自身的访问级别
Xcode会根据Swift代码生成对应的OC声明,写入{targetName}-Swift.h 文件
可以通过@objc 重命名Swift暴露给OC的符号名(类名、属性名、函数名等)
OC调用swift
Swift中依然可以使用选择器,使用#selector(name)定义一个选择器,必须是被@objcMembers或@objc修饰的方法才可以定义选择器
选择器(Selector)n
Swift的字符串类型String,跟OC的NSString,在API设计上还是有较大差异
String的插入和删除
String可以通过下标、 prefix、 suffix等截取子串,子串类型不是String,而是Substring
Substring和它的base,共享字符串数据
Substring发生修改 或者 转为String时,会分配新的内存存储字符串数据
不知道,居然不知道
Substring
String 与 Character
startIndex 、 endIndex 属性、index 方法
String、Array 都遵守了这个协议
String相关的协议nBidirectionalCollection 协议包含的部分内容
pappend、insert、remove 方法
RangeReplaceableCollection 协议包含的部分内容
Dictionary、Set 也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
String相关的协议
多行String
如果你觉得String的API过于复杂难用,可以考虑将String转为NSString
String 与 NSString 之间可以随时随地桥接转换
String使用 == 运算符
NSString使用isEqual方法,也可以使用 == 运算符(本质还是调用了isEqual方法)
比较字符串内容是否等价
String 与 NSString
String ⇌ NSStringString ← NSMutableStringArray ⇌ NSArrayArray ← NSMutableArrayDictionary ⇌ NSDictionaryDictionary ←NSMutableDictionarySet ⇌NSSetSet ←NSMutableSet
Swift、OC桥接转换表
被@objc 修饰的协议,还可以暴露给OC去遵守实现
只能被class继承的协议 protocol Runnable1: AnyObject {}
可选协议n可以通过@objc 定义可选协议,这种协议只能被class 遵守
被 @objc dynamic 修饰的内容会具有动态性,比如调用方法会走runtime那一套流程
dynamicn
block方式的KVO
Swift 支持 KVC \\ KVO 的条件 p属性所在的类、监听器最终继承自 NSObject p用 @objc dynamic 修饰对应的属性
KVC\\KVO
在Swift中,class依然可以使用关联对象
借助关联对象,可以实现类似extension为class增加存储属性的效果
默认情况,extension不可以增加存储属性
关联对象(Associated Object)
资源名管理的其他思路
这种做法实际上是参考了Android的资源名管理方式
资源名管理
异步
延迟
异步延迟
默认自带 lazy + dispatch_once 效果
dispatch_once在Swift中已被废弃,取而代之 p可以用类型属性或者全局变量\\常量
多线程开发 – once
加锁n gcd信号量
多线程开发
预编译宏定义
条件编译
24、从oc到swift
传入函数
映射遍历
排空
与映射遍历的区别
紧凑映射遍历
过滤
例如累加
强大,强大,功能强大
递减计算 reduce
lazy的优化
optional的map和flatMap
Array的常见操作
FP时间
传统写法
函数式写法
第一个参数的通配符
$0是什么?
疑问
FP实践
接收一个或者多个函数作为输入(map、fliter、reduce等)
返回一个函数
高阶函数是至少满足下列一个条件的函数
FP中到处都是高阶函数
高阶函数(Higher-Order Function)
讲一个接收多个参数的函数变换为一系列只接受单个参数的函数
什么是柯里化?
Array、Optional的map方法接收的参数就是一个柯里化函数
函数柯里化
运算符柯里化
柯里化是函数式编程的基础,没有柯里化就没有函数式编程
有什么作用
柯里化(Currying)
像Array、Optional这样支持map运算的类型,称为函子(Functor)
什么是函子
对任意一个函子F,如果能支持一下运算,函子就是一个适用函子
不是很理解不是很理解,看视频,看视频
optional可以成为适用函子
Array可以成为适用函子
适用函子(Applicative Functor)
函子(Functor)
对任意一个类型F,如果能支持以下运算,那么就可以称为是一个单子(Monad)
很显然,Array、Optional都是单子
单子(Monad)
函数式编程(Funtional Programming)
25、函数式编程
在Swift的标准库中,能见到大量POP的影子
面向协议编程(Protocol Oriented Programming,简称POP) p是Swift的一种编程范式, Apple于2015年WWDC提出
同时,Swift也是一门面向对象的编程语言(Object Oriented Programming,简称OOP) p 在Swift开发中,OOP和POP是相辅相成的,任何一方并不能取代另一方,POP能弥补OOP一些设计上的不足
当多个类(比如A、B、C类)具有很多共性时,可以将这些共性抽取到一个父类中(比如D类),最后A、B、C类继承D类
继承的经典使用场合
OOP的三大特性:封装、继承、多态
有些问题,使用OOP并不能很好解决
做法:将run方法放到另一个对象A中,然后BVC、DVC拥有对象A属性 缺点:多了一些额外的依赖关系
做法:将run方法增加到UIViewController分类中缺点:UIViewController会越来越臃肿,而且会影响它的其他所有子类
做法:将run方法抽取到新的父类,采用多继承?(C++支持多继承)缺点:会增加程序设计复杂度,产生菱形继承等问题,需要开发者额外解决
基于OOP想到的一些解决方案?
POP的解决方案
比如 p如何将BVC、DVC 的公共方法run 抽取出来?
为什么要采用POP呢
优先考虑创建协议,而不是父类(基类)
优先考虑值类型(struct、enum),而不是引用类型(class) n 巧用协议的扩展功能
不要为了面向协议而使用协议
POP的注意点
利用协议实现前缀效果
Base: 类
Base: 协议
Base
利用协议实现类型判断
什么是OOP
和OOP的关系?
26、面向协议编程
p ReactiveXü 简称Rx,有众多编程语言的版本,比如RxJava、RxKotlin、RxJS、RxCpp、RxPHP、RxGo、RxSwift等等 ü 官网: http://reactivex.io/ü github: https://github.com/ReactiveX
也是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定 p一般与函数式融合在一起,所以也会叫做:函数响应式编程(Functional Reactive Programming,简称FRP)
什么是RP
简称RAC,有Objective-C、Swift版本
github:https://github.com/ReactiveCocoa
官网: http://reactivecocoa.io/
ReactiveCocoa
比较著名的、成熟的响应式框架
27、响应式编程(Reactive Programming,简称RP)
RxSwift(ReactiveX for Swift),ReactiveX的Swift版本
https://github.com/ReactiveX/RxSwift
源码
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
中文文档
1、Podfile
pod repo update
2、命令行
import RxSwiftimport RxCocoa
3、导入模块
RxSwift的github上已经有详细的安装教程,这里只演示CocoaPods方式的安装
RxSwift:Rx标准API的Swift实现,不包括任何iOS相关的内容
RxCocoa:基于RxSwift,给iOS UI控件扩展了很多Rx特性
模块说明
Observable:负责发送事件(Event)
Observer:负责订阅Observable,监听Observable发送的事件(Event)
next:携带具体数据error:携带错误信息,表明Observable终止,不会再发出事件completed:表明Observable终止,不会再发出事件
三种
Event
创建、订阅Observable1
创建、订阅Observable2
创建Observer
扩展Binder属性
使用
在开发中经常要对各种状态进行监听,传统的常见监听方案有
KVOTarget-ActionNotificationDelegate pBlock Callback
有哪些
传统方案经常会出现错综复杂的依赖关系、耦合性较高,还需要编写重复的非业务代码
缺点
传统的状态监听
RxSwift的状态监听1
RxSwift的状态监听2
它们是RxCocoa.ControlProperty
诸如UISlider.rx.value、UTextField.rx.text这类属性值,既是Observable,又是Observer 类型
既是Observable,又是Observer
每当Observable被订阅时,都会返回一个Disposable实例,当调用Disposable的dispose,就相当于取消订阅
在不需要再接收事件时,建议取消订阅,释放资源。有3种常见方式取消订阅
Disposable
RxSwift的核心角色
28、RxSwift
docs:一些文档stdlib:Swift源码lib:C++源码include:C++头文件
几个可能会经常看的目录
Swift于2015年正式开源
https://github.com/apple/swift
github地址: https://github.com/apple/swift
标准库源码位置:https://github.com/apple/swift/tree/master/stdlib/public/core
Swift源码简介
https://github.com/apple/swift/blob/master/stdlib/public/core/Sequence.swift
map、filter
https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceAlgorithms.swift
flatMap、compactMap、reduce
Array分析
https://github.com/apple/swift/blob/master/stdlib/public/core/Substring.swift
append、lowercased、uppercased
Substring分析
https://github.com/apple/swift/blob/master/stdlib/public/core/Optional.swift
map、flatMap、==、??
Optional分析
文档: https://github.com/apple/swift/blob/master/docs/ABI/TypeMetadata.rst
https://github.com/apple/swift/blob/master/include/swift/ABI/Metadata.h
https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataKind.def
https://github.com/apple/swift/blob/master/include/swift/ABI/MetadataValues.h
https://github.com/apple/swift/blob/master/include/swift/Reflection/Records.h
其他参考
对于任意一个类型,都能够动态获取这个类的所有属性和方法信息
对于任意一个实例,都能够动态调用它的任意方法和属性
反射是编程语言中一项强大的能力,比如Java语言的反射机
Swift的反射机制目前还比较弱,通过 Mirror 类型来提供简单的反射功能
反射
Metadata分析
28、标准库源码分析
https://github.com/Alamofire/Alamofire
网络请求
https://github.com/onevcat/Kingfisher
图片下载
https://github.com/SwiftyJSON/SwiftyJSON
JSON访问
https://github.com/kakaopensource/KakaJSON
JSON-Model转换
常用Swift第三方库
pod 'KingfisherWebP'
Kingfisher默认不支持WebP格式的图片,需要额外安装KingfisherWebP
Kingfisher注意点
以上库使用的注意点
默认情况下,用到哪个库就要导入哪个库,无疑增加了很多重复的工作量
新建一个用于Swift调用OC的桥接文件: targetName-Bridging-Header.h p导入系统库:#import <XX/XX.h> p导入第三方库(Framework形式):#import <XX/XX-Swift.h>
如何办到全局导入库?
库的导入问题
29、项目实战
// 1在Swift中,内存管理是通过引用计数(reference counting)来实现的。Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用。当你创建一个类实例时,Swift会自动分配内存来存储该实例,并且会在不再需要该实例时释放这些内存。ARC会在每个类实例被创建时维护一个引用计数。当你将一个新的常量或变量设置为对该实例的引用,引用计数就会增加。相反,当你将对该实例的引用赋值给另一个变量、常量或者当它离开其当前范围时,引用计数就会减少。当引用计数变为零时,ARC会自动释放该实例所占用的内存。但是,由于循环引用的问题,ARC可能会导致内存泄漏。循环引用发生在两个或多个对象相互保持对彼此的强引用时,这意味着它们互相阻止彼此被释放。为了解决这个问题,Swift提供了弱引用(weak reference)和无主引用(unowned reference)的概念,使得在引用对象之间建立一种“非占有关系”,从而打破循环引用。- 弱引用:不会增加对象的引用计数,并且当引用的对象被释放时,弱引用会自动变为nil。- 无主引用:也不会增加对象的引用计数,但是它假定引用的对象在整个生命周期中都存在,并且不会被释放。通过合理使用弱引用和无主引用,可以避免循环引用导致的内存泄漏问题。 //2Swift的内存管理基于自动引用计数(Automatic Reference Counting,ARC)原理。ARC跟踪并计算你应用程序中的每个实例的引用次数,当引用次数变为0时,自动释放这些实例占用的内存。下面是ARC的一般工作原理:1. **引用计数**:当你创建一个新的类实例时,ARC会分配一块内存来存储这个实例,并将引用计数初始化为1。每当有新的变量或常量引用这个实例时,引用计数会加1;当引用被移除时,引用计数会减1。引用计数为0时,ARC会释放该实例所占用的内存。2. **循环引用**:ARC是智能的,但仍然容易发生循环引用。循环引用会导致内存泄漏,因为对象之间的强引用形成了闭环,导致它们永远不会被释放。为了避免循环引用,Swift提供了弱引用(weak references)和无主引用(unowned references)的概念。弱引用不会增加引用计数,且当对象被释放时会自动变为nil;无主引用假定引用的对象在整个生命周期中都存在,且不会被释放。3. **自动管理**:Swift的ARC是自动管理的,这意味着你无需手动添加释放代码。当你的应用程序执行时,ARC会自动跟踪和管理对象的引用次数。4. **性能优化**:ARC会在合适的时机插入释放代码,以确保对象占用的内存能够及时释放,但也尽量减少额外的开销。总的来说,Swift的内存管理依靠ARC,它是一种自动化的内存管理机制,可以确保你的应用程序在使用内存时保持高效和稳定。
内存管理
30、专项
pdf阅读
其值不需要在编译时期确定
运行时语言
mutating
@discardableResult
特性
frame variable -R
fr v -R
查看内存结构
lldb指令
技巧
单例模式
设计模式
ASCII码表:https://www.ascii-code.com/
其他资源
swift
收藏
收藏
0 条评论
回复 删除
下一页